keskiviikko 16. maaliskuuta 2011

Parsing command-line arguments with Haskell

I'm writing this because I found yet another great thing about Haskell: it's actually a nice language for writing command-line tools! A command-line tool written in Haskell reads like the manual for the tool. Pattern matching makes it easier and cleaner than in any other language (prove me wrong).

You can match exact strings for commands like "init", "update", "status", as well as any number of following arguments that you can use in the implementation of that command.

    main = getArgs >>= rebass

rebass ["init", name] = do
-- init using given name, ignore rest of arguments
rebass ["update"] = do
-- update, ignore arguments
rebass ["status"] = do
-- show status, ignore arguments
rebass _ = do
-- show usage as none of the patterns matched

Here's what I'm working on btw: https://github.com/raimohanska/rebass

tiistai 15. maaliskuuta 2011

Cooking delicious fish

Many Haskell newcomers stumble when presented with wonderful but abstract utility functions from the stdlib. This is mostly because Haskell documentation lacks easy to understand usage examples. In this short entry I will introduce one such nice function and an example how to use it.

Like a good Haskell citizen, let's start with its type signature:

    (a -> m b) -> (b -> m c) -> (a -> m c)

So, those are the ingredients that go along with a fish >=>, the name of the function1. Operators (functions with symbolic names) in Haskell are used in an infix form:

    x1 >=> x2

Here x1 has a type (a -> m b) and x2 has a type (b -> m c). The type of x1 >=> x2 is therefore (a -> m c). Now let's put all that aside for awhile and approach this example from a different angle.

Parse, convert and validate

A common programming task in a networked world is to parse some input data coming from a user. It needs to be parsed, converted to a proper type and often validated too.

    parseAge :: String -> Maybe Int
parseAge s = case reads s of
[(age, "")] -> Just age
_ -> Nothing

maxVal :: Int -> Int -> Maybe Int
maxVal x y | y > x = Nothing
| otherwise = Just y

minVal :: Int -> Int -> Maybe Int
minVal x y | y < x = Nothing
| otherwise = Just y

I'm using a Maybe type here since it is the most simple type which can represent failed computations. It is easy to replace that with a more sophisticated type, one capturing the failure reasons and other stuff but to keep the example short we will use a simpler type now. Anyway, given a set of such utility functions we would like to compose more complex functionality. This is where our fish excels.

    parseAge >=> (minVal 18) >=> (maxVal 24)

The above expression has a type (String -> Maybe Int) and it works as expected. We'll get Just x if age can be parsed as an integer, and it is at least 18 but at most 24. Otherwise we'll get Nothing.

    > :m +Control.Monad
> let pAge = parseAge >=> (minVal 18) >=> (maxVal 24)
> pAge "20"
Just 20
> pAge "20a"
Nothing
> pAge "17"
Nothing
> pAge "25"
Nothing

Conclusion

Functions are often composed in Haskell with . function. However, that simple composition function won't work if the functions composed are monadic functions (a -> m b). Monadic functions can be composed with >=> function.


  1. The real name of the function is Kleisli composition, but the operator symbol selected for that function looks a lot like a fish. ↩

maanantai 14. maaliskuuta 2011

Haskell Knows Your Latvian Name

My colleague presented a devilishly clever, yet simple algorithm for generating a Latvian version of your name. Here's the implementation in Haskell:

    import System.Environment(getArgs)
main = do
args <- getArgs
putStrLn $ latvian args
where
latvian = unwords . map (++ "s")

Copy this code to a file, say latvian.hs then

    runhaskell latvian.hs <your name>

And you'll know what you'd be called in case you were lucky enough to be born in Latvia.'

sunnuntai 13. maaliskuuta 2011

How to set up Haskell on Mac OS X

This guide assumes, that you have the brilliant Homebrew installed. If not, I recommend that you go to the Homebrew homepage and install it first.

Resetting Cabal and GHC package database

Sometimes you fiddle around your installation, and end up with messed up system that won't compile anything non-trivial. Cabal might have gotten into twist. In this case, the easiest solution sometimes is to do a reset and reinstall. It won't take much time, but allows you to have clean slate.

Magic incatations needed for cleanup are following:

    brew uninstall haskell-platform ghc  
rm -rf ~/.ghc ~/.cabal

These commands uninstall ghc (the haskell compiler) and haskell-platform (the standard library) and remove local databases.

Set up Haskell

Using brew, install haskell-platform with ghc (if brew formula for ghc is missing, it's likely that ghc has been rolled into haskell-platform).

    brew install ghc haskell-platform

After the haskell-platform has been installed, update cabal database with command

    cabal update

Add cabal binaries to PATH

This step depends a lot on your default shell. For bash, open ~/.bashrc to an editor of your choice, and add following line to the end:

    export PATH=$PATH:~/.cabal/bin

Next, take the new PATH to use with command

    source ~/.bashrc

Final words

Now you should have a working Haskell installation. Please note that sometimes your cabal installation may go corrupt (unsatiable or conflicting dependences) - in this case, you may need to use the reset described in the (first section.