Showing posts with label Haskell. Show all posts
Showing posts with label Haskell. Show all posts

Friday, August 17, 2012

Beyond Package Version Policies

When I read the announcement of the latest GHC release candidate, I did not feel excitement but rather annoyance. The reason is that now I have to go and check all my packages' dependency specifications and see if they require a version bump. I was not the only one. In the following I sketch an approach that I think could take out most of the pain we currently experience in Hackage ecosystem.

The Problem


The reason for this annoyance is the rather strict Hackage package versioning policy (PVP). The PVP specifies the format of the version number of a Hackage package as a sequence of four integers separated by dots:

majorA.majorB.minor.patchlevel

The top two digits denote the major version number, and must be incremented if a potentially breaking change is introduced in a new package release. The minor number must be incremented if new functionality was added, but the package is otherwise still compatible with the previous version. Finally, a patchlevel increment is necessary if the API is unchanged and only bugfixes or non-functional changes (e.g., documentation) were made. This sounds fairly reasonable and is basically the same as what is used for shared libraries in the C world.

When specifying dependencies on other packages, authors are strongly encouraged to specify upper bounds on the major version. This is intended to avoid breaking the package if a new major version of the dependency is released (Cabal-install always tries to use the latest possible version of a dependency). If the package also works with a newer version of the dependency, then the author is expected to release a new version of his/her library with an increased upper bound for the dependency version.

 In Haskell, unfortunately, this system doesn't work too well for a number of reasons:

  1. Say, my package P depends on package A-1.0 and I now want to test if it works with the newly released version A-1.1. My package also depends on package B-0.5 which in turn also depends on A-1.0. GHC currently cannot link two versions of the same package into the same executable, so we must pick one version that works with both -- in this case that's A-1.0. D'oh!
    I now have two options: (a) wait for the author of package B to test it against A-1.1, or (b) do it myself. If I choose option (b) I also have to send my patch to the author of B, wait for him/her to upload the new version to Hackage and only then can I upload my new version to Hackage. The problem is multiplied by the number of (transitive) dependencies of my package and the number of different authors of these packages. This process takes time (usually months) and the fast release rate of GHC (or many other Haskell packages, for that matter) doesn't make it any easier.
  2. Packages get major-version upgrades rather frequently. One reason is that many Haskell libraries are still in flux. Another is that if a package adds a new instance, a major version upgrade is required. We can protect against new functions/types being added to a package because we can use explicit import lists. New instances are imported automatically, and there's no way to hide them when importing a module.
  3. A package version is a very crude and conservative approximation that a dependent package might break.
Generally, I think it's a good thing that Haskell packages are updated frequently and improved upon. The problem is that the current package infrastructure and tools don't work well with it. The PVP is too conservative.

A Better Approach


The key notion is to track dependencies at the level of individual functions, types, etc. rather than at the level of whole packages.

When a package P depends on another package A it usually doesn't depend on the whole package. Most of the time P just depends on a few functions and types. If some other part of A is changed, that shouldn't affect P. We have so much static information available to us, it's a shame we're not taking advantage of it. Consider the following system:
  1. When I compile my code, the compiler knows exactly which functions, types, etc. my program uses and from which packages they come from. The compiler (or some other tool) writes this information to a file (preferably in a human-readable format). Let's call this file: dependencies.manifest
  2. Additionally, the compiler/tool also generates a list of all the functions, types, etc. defined by code in my package. Let's call that file: exports.manifest. I believe GHC's ABI versioning already does something very similar to this, although it just reduces this all to a hash.
The first use of this information is to decide whether a package is compatible with an expected dependency. So, if my package's "dependency.manifest" contained (for example)

type System.FilePath.Posix.FilePath = Data.String.String
System.FilePath.Posix.takeBaseName :: System.FilePath.Posix.FilePath -> System.FilePath.Posix.FilePath

then it is compatible with any future (or past) version of the filepath package that preserves this API and that defines FilePath as a type synonym for Strings.

Of course, this only checks for API name and type compatibility, not actual semantic compatibility. This requires some hints from the package authors, as described below. Together with annotations from the package author about semantic changes, the only information we need to check if a newer package is a compatible dependency are the versions the original versions of the dependencies used and the manifest of the new package.

For example, let's say version 0.1 of my package looks as follows:

module Gravity where
bigG :: Double -- N * (m / kg)^2
bigG = 6.674e-11
force :: Double -> Double -> Double -> Double -- N
force m1 m2 r = (bigG * m1 * m2) / (r * r)

Its manifest will look something like this:

Gravity.bigG :: Double, 0.1
Gravity.force :: Double -> Double -> Double -> Double, 0.1

The version of each item is the version of the package at which it was introduced or changed its semantics.

Now I add a new function in version 0.1.1:

standardGravity :: Double -- m/s^2
standardGravity = 9.80665

The manifest for version 0.1.1 now will be

Gravity.bigG :: Double, 0.1
Gravity.force :: Double -> Double -> Double -> Double, 0.1
Gravity.standardGravity :: Double, 0.1.1

Now, let's say I want to improve the accuracy of bigG in version 0.2:

bigG = 6.67384

Since bigG was changed and force depended upon it, by default the new manifest would be:

Gravity.bigG :: Double, 0.2
Gravity.force :: Double -> Double -> Double -> Double, 0.2
Gravity.standardGravity :: Double, 0.1.1

However, one could argue that this is a backwards compatible change, hence the manifest would be adjusted by the author (with the help of tools) to:

Gravity.bigG :: Double, 0.1
Gravity.force :: Double -> Double -> Double -> Double, 0.1
Gravity.standardGravity :: Double, 0.1.1

That is the same manifest as version 0.1.1, thus 0.2 is 100% compatible with all users of 0.1.1 (according to the package author), and even all users of 0.1 because no functionality has been removed.

Even if manifests didn't include the version number (for now) I believe just the API information is precise enough for most cases. It will still be necessary to constrain the allowed range of package dependencies, but that should be the rare exception (e.g., a performance regressions) rather than the current state where dependencies need to be adjusted every few months.

Upgrade Automation


This mechanism alone only helps with being less conservative when checking whether a package can work with an updated dependency. The other issue is that Haskell package APIs are often moving quickly and thus breaking code is unavoidable. If a package only has a few dependents this may not be such a big deal, but it becomes a problem for widely used packages. For example, during the discussions for including the vector package into the Haskell Platform some reviewers asked for functions to be moved from one module into the other. Roman, vector's maintainer, argued against this noting it would break many dependencies -- a valid concern. Even if this was only a small issue, fear of breaking dependent packages can slow down improvements in package APIs.

The Go programming language project has a tool called "gofix", which can automatically rewrite code for simple API changes and generates warnings for places that require human attention. Haskell has so much static information, that such a tool is quite feasible (e.g., HaRe can already do most of the important bits).

So, I imagine that a newly-released package specifies up to two additional pieces of information:

  • An annotated manifest indicating where semantic changes were made while retaining the same API. This can be seen as bumping the version of a single function/type, rather than of the whole API. To avoid the impact of human error this, too, should be tool supported. For example, if we compute an ABI hash for each function, we can detect which functions were modified. The package author can then decide if that was just a refactoring or an actual semantic change.
    (This has to be done with the help of tools. Imagine we refactor a frequently used internal utility function. Then all functions that use it would potentially have changed semantics. However, as soon that function is marked as backwards compatible, so will all its users. So it's important that a tool asks the package author for compatibility by starting with the leaf nodes.)
  • Optionally, the author may specify an upgrade recipe to be used by an automated tool or even just a user of the library. This could include simple instructions like renaming of functions (which includes items moved between modules or even packages), or more complicated things like a definition of a removed function in terms of newly-added functions. For more complicated changes a textual description of the changes can give higher-level instructions for how to manually upgrade. Since this should be human-readable anyway, we may as well specify this upgrade recipe in a (formally defined) format that looks like a Changelog file.

    Summary


    The PVP doesn't work well because it is too conservative and too coarse-grained. Haskell contains enough static information to accurately track dependencies at the level of functions and types. We should take advantage of this information.

    The ideas presented above certainly require refinement, but even if we have to be conservative in a few places (e.g., potentially conflicting instance imports), I think it will still be much less painful than the current system.

    Comments and constructive critiques welcome!

    Friday, April 30, 2010

    Haskell Tip: Redirect stdout in Haskell

    Have you ever wanted to make sure that a call to a library cannot print anything to stdout? The following does this except that it redirects stdout globally and not just across a library call. This should be doable, but I haven't needed it yet.
    import GHC.IO.Handle   -- yes, it's GHC-specific
    import System.IO
    
    main = do
      stdout_excl <- hDuplicate stdout
      hDuplicateTo stderr stdout  -- redirect stdout to stderr
      
      putStrLn "Hello stderr" -- will print to stderr
      hPutStrLn stdout_excl "Hello stdout" -- prints to stdout
    The above code first creates a new handle to the standard output resource using hDuplicate. The call to hDuplicateTo redirects any output to the Haskell handle stdout to go to the handle stderr. The Haskell handle stdout_excl is now our only handle to the standard output resource.

    Sunday, March 16, 2008

    A short reminder

    Folds and maps are Haskell's default iteration combinators. Mapping is easy enough, but folds can often be rather messy, especially if nested. For example, given a map of sets of some values, we want to write a function to swap keys and values. The function's type will be:
    
    type SetMap k a = Map k (Set a)
    
    invertSetMap :: (Ord a, Ord b) => SetMap a b -> SetMap b a
    
    The resulting map should contain a key for each value of type b, occurring in any set in the original map. The new values (of type Set a) are all those original keys for which the new key occurred in the value set. Intuitively, if the original set was representing arrows from values of type a to values of type b, this function should reverse all arrows. We can easily implement this function using two nested folds.
    
    invertSetMap sm = 
      M.foldWithKey 
          (\k as r -> 
              S.fold (\a r' -> M.insertWith S.union a (S.singleton k) r')
                     r
                     as)
          M.empty
          sm    
    
    That's not pretty at all! I had written quite a bit of this kind of code (and hated it each time), until I finally remembered a fundamental Haskell lesson. Haskell uses lists to simulate iteration and specify other kinds of control flow. In particular list comprehensions are often extremely cheap, since the compiler can automatically remove many or all intermediate lists and generate very efficient code. So let's try again.
    
    invertSetMap sm = M.fromListWith S.union
        [ (a, S.singleton k) | (k, as) <- M.assocs sm
                             , a       <- S.toList as ]
    
    So much more readable! A quick benchmark also shows that it's slightly faster (a few percent for a very big map). Lesson to take home: If your folds get incomprehensible consider list comprehensions.

    Friday, October 05, 2007

    New Haskell Tutorial

    Conrad Barski recently made a new Haskell Tutorial available. A while ago I stumbled upon Conrad's excellent Lisp Tutorial which was well-received in the community and actually lead to a pretty cool (Common) Lisp-logo. I haven't yet read the tutorial completely, but they are usually very well-written, newbie-friendly and based on interesting problems. So, check it out!

    PS: Greetings from the second Hackathon 2007.

    Monday, May 21, 2007

    Network.HTTP + ByteStrings

    Update: I mixed some numbers. I wrote about 375 MB, but it were 175 MB. (Noone seemed to have noticed though. Anyways, the argument still holds.)

    Haskell's Network.HTTP package isn't quite as good as it could be. Well, to be precise, it is not at all as good as it should be. In addition to API problems (for which I proposed a solution in my previous blog entry) there's also a major performance problem, due to strictness and use of regular list-based Strings. A simple wget-style program written in Haskell used like ./get http://localhost/file.big on a local 175 MB file almost locked my 1GB laptop due to constant swapping. I had to kill it, as it was using up more than 500 MB of RAM (still swapping). At this point it had run for 50 seconds at had written not a single byte to the output file. At the same time a normal wget completed after abound 10 seconds. Since the file was retrieved from a local server I assume overall performance was inherently limited by disk speed (or the operating system's caching strategy). The current implementation performed so badly for two reasons:
    • Since it uses list-based strings each retrieved byte will take up (at least) 8 byte in program memory (one cons cell, or tag + data + pointer to tail).
    • It implements custom, list-based buffering. The buffer size is 1000 characters/bytes, which is rather OK for line-based reading, but if the HTTP protocol requests to read a large block of data, this block will be read in 1000 byte chunks and then be appended to the part that has alrady been read. So if we read a block of 8000 bytes, the first block will be read and consequently be copied 8-times(!). Let's not think about reading a block of 175000000 bytes. Also because we already know the answer.
    But let's not flame the original author(s). It's better than nothing and it gave me and my project partner Jonas an interesting project topic. So we decided to overcome the evil at its root and replace Strings using ByteStrings--this way we would get buffering for free. To give you a taste for what this accomplishes:
    ProgramRuntimeMemory Use
    wget~10s~0.5MB
    ./get using strict ByteStrings~18s~175MB
    ./get using lazy ByteStrings~11s~3MB
    Adding strict ByteStrings was relatively straightforward. Network.HTTP already implements a simple Stream abstraction with a simple interface:
    class Stream x where 
        readLine   :: x -> IO (Result String)
        readBlock  :: x -> Int -> IO (Result String)
        writeBlock :: x -> String -> IO (Result ())
        close      :: x -> IO ()
    
    Implementing this for strict ByteStrings is just a matter of calling the corresponding functions from the ByteStrings module. With one small annoyance: The HTTP parsing functions expect readLine to return the trailing newline, which hGetLine does not include, so we have to append it manually, which in turn is an O(n) operation. For simplicity, we also didn't convert the header parsing and writing functions to use ByteStrings, but instead inserted the appropriate calls to pack and unpack. This could become a performance bottleneck if we have many small HTTP requests. OTOH, we might soon have a Parsec version that works on ByteStrings. As could be seen from the above benchmarks, using strict ByteStrings still forces us to completely load a packet into memory before we can start using it, which may result in unnecessary high memory usage. The obvious solution to this problem is to use lazy ByteStrings. For lazy ByteStrings things work a bit differently. Instead of calling hGet and hGetLine inside the stream API, we call hGetContents when we open the connection. This gives us a lazy ByteString which we store in the connection object and then use regular list functions on that string to implement the required API.
    openTCPPort uri port = 
        do { s <- socket AF_INET Stream 6
           -- [...]
           ; h <- socketToHandle s ReadWriteMode
           ; bs <- BS.hGetContents h  -- get the lazy ByteString
           ; bsr <- newIORef bs       -- and store it as an IORef
           ; v <- newIORef (MkConn s a h bsr uri) 
           ; return (ConnRef v)
           }
    
    readBlock c n =
            readIORef (getRef c) >>= \conn -> case conn of
              ConnClosed -> return (Left ErrorClosed)
              MkConn sock addr h bsr host ->
                  do { bs <- readIORef bsr 
                     ; let (bl,bs') = BS.splitAt (fromIntegral n) bs
                     ; writeIORef bsr bs'  
                     ; return $ Right bl
                     }
       
        readLine c =
            readIORef (getRef c) >>= \conn -> case conn of
              ConnClosed -> return (Left ErrorClosed)
              MkConn sock addr h bsr host ->
                  do { bs <- readIORef bsr
                     ; let (l,bs') = BS.span (/='\n') bs
                     ; let (nl,bs'') = BS.splitAt 1 bs'
                     ; writeIORef bsr bs''
                     ; return (Right (BS.append l nl)) -- add '\n'
                     }
            `Prelude.catch` \e -> [...]
    
    There are two main problems with this implementation, though:
    • ByteStrings currently only work on handles not on sockets. Thus we have to turn sockets into handles using socketToHandle which, according to the source code linked from the Haddock documentation will fail if we're in a multithreaded environment. (search for "PARALLEL_HASKELL" in Network.Socket's source.
    • Furthermore, after converting a socket to a handle we should no longer use this socket. So we can't change any settings of the socket, but close it by calling hClose on the handle. HTTP allows the user to specify whether a socket should be closed after the response has been received. This is a bit more tricky when we use lazy ByteStrings since our request function will return immediately with a lazy ByteString as a result but no data has been read (except, maybe, on block). We thus must not close the socket right away, but only after all its contents have been read. So we must rely on hGetContents to close our handle (and thus socket) -- which is does not! From recent #haskell comments this seems to be bug. In any case though we'd want to be able to specify the behavior, as we might as well keep the socket open.
    There are further issues to consider. E.g., can we rely on the operating system to buffer everything for us if we don't read it right away? I don't know the details, but I assume this is handled by some lower layer, possibly dropping packages and re-requesting them if necessary. That's just guessing though. Unfortunately, I will not have the time to work out these issues anytime soon, as I will be busy with my Google Summer of Code project (cabal configurations). There also is a SoC project to replace Network.HTTP with libcurl bindings, but it would probably be a good idea to still have a reasonable Haskell-only solution around. So if anyone wants to pick it up, you're welcome! You can get the sources for the lazy version with darcs get http://www.dtek.chalmers.se/~tox/darcs/http and for the strict version darcs get http://www.dtek.chalmers.se/~tox/darcs/http-strict If you're interested you can take a look at our project page.

    Monday, May 07, 2007

    Towards Better Error Handling

    A while ago Eric Kidd wrote a rant about inconsistent error reporting mechanisms in Haskell. He found eight different idioms, none of which were completely satisfying. In this post I want to propose a very simple but IMO pretty useful and easy-to-use scheme, that works with standard Haskell.

    The Haskell HTTP Package is a good test case for such scheme. The most immediate requirements are:

    • It should work from within any monad (not just IO).
    • It should be possible to catch and identify any kind of error that happened inside a call to a library routine.
    • It should be possible to ignore the error-handling (e.g., for simple scripts that just die in case of error)

    So far, the public API functions mostly have a signature like

    type Result a = Either ConnError a
    
    simpleHTTP :: Request -> IO (Result Response)
    

    This requires C-style coding where we have to check for an error after each call. Additionally, we might still get an IOException, and have to catch it somewhere else (if we want to). A simple workaround is to write a wrapper function for calls to the HTTP API. For example:

    data MyErrorType = ... | HTTPErr ConnError | IOErr IOException
    instance Error MyErrorType where
        noMsg    = undefined  -- who needs these anyways?
        strMsg _ = undefined
    
    instance MonadError MyErrorType MyMonad where ...
    
    -- | Perform the API action and transform any error into our custom
    --   error type and re-throw it in our custom error type.
    ht :: IO (Result a) -> MyMonad a
    ht m = do { r <- io m
              ; case r of
                  Left cerr -> throwError (HTTPErr cerr)
                  Right x   -> return x
              }
    
    -- | Perform an action in the IO monad and re-throw possible
    --   IOExceptions as our custom error type.
    io :: IO a -> MyMonad a
    io m = do { r <- liftIO $
                       (m >>= return . Right)
                       `catchError` (\e -> return (Left e))
              ; case r of
                  Left e -> throwError (IOErr e)
                  Right a -> return a
              }
    

    We defined a custom error type, because we can have only one error type per monad. Exceptions in the IO monad and API error messages are then caught immediately and wrapped in our custom error type.

    But why should every user of the library do that? Can't we just fix the library? Of course we can! Now, that we have a specific solution we can go and generalize. Let's start by commenting out the type signatures of ht and io and ask ghci what it thinks about the functions' types:

    *Main> :t io
    io :: (MonadIO m, MonadError MyErrorType m) => IO a -> m a
    *Main> :t ht
    ht :: (MonadIO t, MonadError MyErrorType t) =>
    IO (Either ConnError t1) -> t t1
    

    Alright, this already looks pretty general. There's still our custom MyErrorType in the signature, though. To fix this we apply the standard trick and use a type class.

    data HTTPErrorType = ConnErr ConnError | IOErr IOException
    
    -- | An instance of this class can embed 'HTTPError's.
    class HTTPError e where
        fromHTTPError :: HTTPErrorType -> e
    

    Our wrapper functions now have a nice general type, that allows us to move them into the library.

    throwHTTPError = throwError . fromHTTPError
    
    ht :: (MonadError e m, MonadIO m, HTTPError e) =>
          IO (Result a) -> m a
    ht m = do { r <- io m
              ; case r of
                  Left cerr -> throwHTTPError (ConnErr cerr)
                  Right a   -> return a
              }
    
    -- | Perform an action in the IO monad and re-throw possible
    --   IOExceptions as our custom error type.
    io :: (MonadError e m, MonadIO m, HTTPError e) =>
          IO a -> m a
    io m = do r <- liftIO $
                     (m >>= return . Right) 
                     `catchError` (\e -> return (Left e))
              case r of
                Left e  -> throwHTTPError (IOErr e)
                Right a -> return a
    

    After wrapping, all exported functions will have a signature of the form:

    f :: (MonadError e m, MonadIO m, HTTPError e) =>
         ... arguments ... -> m SomeResultType
    

    Now the user is free to choose whichever monad she wants (that allows throwing errors and I/O). The only added burden is for the user to specify how to embed a HTTPError in the respective error type of the monad. We can already specify the instance for IO, though.

    instance HTTPError IOException where
        fromHTTPError (IOErr e) = e
        fromHTTPError (ConnErr e) = userError $ show e
    

    This way, our modified API works nicely out of the box whenever we just use the IO monad and we can use it in our custom monad by writing only one simple instance declaration.

    data MyErrorType = ... | HTTPErr HTTPErrorType
    
    instance HTTPError MyErrorType where
        fromHTTPError = HTTPErr
    
    test1 req = do { r <- simpleHTTP req
                   ; putStrLn (rspCode r)
                   } `catchError` handler
      where handler (HTTPErr (ConnErr e)) = putStrLn $ "Connection error."
            handler (HTTPErr (IOErr e))   = putStrLn $ "I/O Error."
            handler _                     = putStrLn $ "Whatever."
    

    If we don't care about the error and thus don't want to implement the instance, we can still force our API to be in the IO monad and thus reuse IOException to embed possible HTTP errors.

    test2 req = do { r <- liftIO $ simpleHTTP req
                   ; putStrLn (rspCode r)
                   }
    

    I think this is a very simple but useful scheme. I already implemented this with a friend in the HTTP package—and it works (without -fglasgow-exts).

    In addition to the added type class, there is the further potential drawback that an IOException will always be wrapped in an API-specific error type. So when a program uses more than one API that uses this scheme, an IOException may be wrapped in either, which may or may not be what is desired. A more sophisticated system, that deals with this problem and provides additional features, is explain in Simon Marlow's paper "An Extensible Dynamically-Typed Hierarchy of Exceptions" (PDF).

    Comments welcome.

    Wednesday, December 20, 2006

    More on Syntax

    My last post appeared on reddit--thanks dons! This induced some comments I'd like to respond to. First of all, there already is a macro system for Haskell, called (somewhat misleadingly) Template Haskell. It already provides the capabilities to generate arbitrary Haskell code. (More correctly, Haskell 98 code, since extensions like Generalized ADTs are not supported.) It also provides the given quasi-quotation mechanism I used in my last post's mock-ups: [| ... |]. However it has two problems. Firstly, macros are marked specially using the $(macro ...) syntax, which is not as seamless as it could be, although there might be good reasons to keep it, namely to make it easily recognizable, when macros are involved. Secondly, its quasi-quotation syntax is very limited, i.e., you cannot introduce new bindings and it's hard to modularize code--but I might be wrong with here since I might not have pushed it as far as possible. The problem is: when you cannot use the quasi-quotation syntax then you're left building up the quite complex Haskell parse tree yourself. Due to limited documentation and Haskell's syntax rules you usually write your macros by first getting the AST of some sample code, e.g. using:
    -- | print a human-readable representation of a given AST
    printAST :: ExpQ -> IO ()
    printAST  ast = runQ ast >>= putStrLn . show
    
    pp = printAST [| let x = $([|(4+)|]) in x 5 |]
    which then (reformatted) looks like this:
    $ pp
    LetE [ValD (VarP x_0)
               (NormalB (InfixE (Just (LitE (IntegerL 4)))
                                (VarE GHC.Num.+) Nothing))
                         []]
         (AppE (VarE x_0) (LitE (IntegerL 5)))
    Then you try to customize this for your purposes. Not pretty. My actual attempt was to take a type name as a parameter, inspect it, and then generate some boilerplate code. Well, I tried but gave up after being unable to construct some type. Maybe I didn't try hard enough. Anyways, macro-writing should be that hard! My proposed solution certainly is just a sketch of an idea, essentially pointing to prior art. I don't claim that this will in fact work nicely or even that it will work at all. I am pretty confident that it might, though, and I am planning to give it a shot later on. Maybe extending Template Haskell with features similar t o Scheme's syntax-case might be enough, for a start. And yet, I don't consider this a high-priority project, since a lot of uses for macros in Lisp can be solved differently in Haskell, as has also been mentioned in the comments to my previous post:
    • Controlling the order of evaluation is not necessary in Haskell since, due to lazyness. And if we have to control it somehow, we mostly use monads.
    • The whole category of (with-something (locally bound vars) ...) can be implemented almost as conveniently using withFoo \locally bound vars -> do ...
    • A lot of cases for special syntax can be achieved using clever operator and constructor naming. E.g., in wxHaskell: t <- timer f [interval := 20, on command := nextBalls vballs p], or, for an in-progress project of mine I simulate a convenient assembler syntax by allowing a notation like: res <-- a `imul` c. However, I was not able to use the <- notation, since I have different scoping rules than Haskell and I'm not in a monad.
    • Many cases of boilerplate code generation can be covered using generic programming, e.g. using Scrap Your Boilerplate.
    So where would (more usable) macros still make sense?
    • Allow more flexible syntax for domain-specific embedded languages (DSELs), e.g. an XML library or a parser library might profit from this right now. (Yes, I think Parsec could be more readable). Also, DSLs like Happy would be even nicer if embedded directly into Haskell. Arrows and Monads were considered general enough concepts to introduce new syntax for them, but I think there's more out there that deserves it. I also think that an upcoming project of mine might hit the limits of what's currently possible in Haskell. Some people seem to agree.
    • Speaking of ParseC there's still one common use for Lisp-macros: optimizing at compile-time. You can get quite far by carefully designing your combinators for your DSELs. However, combining nice syntax and performance is very hard. In Lisp, the loop embedded language, for example, does quite heavy transformations on the given code. Partial evaluation is probably the more general solution here, but it seems to be not quite ready for primetime, yet.
    • The point, that a powerful enough system would essentially make syntactic sugar a library can be seen as a positive side effect, too. But I think this doesn't have much practical significance.
    Bottom line: There certainly are less uselful applications of macros in Haskell than, e.g. in Lisp, but there are serious enough arguments to at least consider them.

    Friday, November 10, 2006

    Being Lazy

    Lazy? Me? No. Noo. Never! But here's a Reddit discussion (warning: long!) that--even though blown up by an obvious troll--has some nice statements about performance, usability, and composability implications of lazy evaluation. Quite interesting (if you filter out the noise).