· haskell

Haskell: Processing program arguments

My Prismatic news feed recently threw up an interesting tutorial titled ‘Haskell the Hard Way’ which has an excellent and easy to understand section showing how to do IO in Haskell.

About half way down the page there’s an exercise to write a program which sums all its arguments which I thought I’d have a go at.

We need to use the System.getArgs function to get the arguments passed to the program. It has the following signature:

> :t getArgs
getArgs :: IO [String]

Using that inside a ‘do’ block means that we can get the list of arguments as a List of String values.

We then need to work out how to convert the list of strings into a list of integers so that we can add them together.

The way I’ve done that before is with the read function:

> map (\x -> read x :: Int) ["1", "2"]

That works fine as long as we assume that only numeric values will be passed as arguments but if not then we can end up with an exception:

> map (\x -> read x :: Int) ["1", "2", "blah"]
[1,2,*** Exception: Prelude.read: no parse

I wanted to try and avoid throwing an exception like that but instead add up any numbers which were provided and ignore everything else. The type signature of the function to process the inputs therefore needed to be:

[String] -> [Maybe Int]

With a bit of help from a Stack Overflow post I ended up with the following code:

import Data.Maybe

intify :: [String] -> [Maybe Int]
intify =  map maybeRead2

maybeRead2 :: String -> Maybe Int
maybeRead2 = fmap fst . listToMaybe . reads

reads has the following signature:

> :t reads
reads :: Read a => ReadS a

which initially threw me off as I had no idea what ‘ReadS’ was. It’s actually a synonym:

type ReadS a = String -> [(a,String)]

(defined in ./Text/ParserCombinators/ReadP.hs)

In our case I thought it’d do something like this:

> reads "1"
[(1, "1")]

But defining the following in a file:

a :: [(Int, String)]
a = reads "1"

suggests that the string version gets lost:

> a

I’m not sure I totally understand how that works!

Ether way, we then take the list of tuples and convert it into a Maybe using listToMaybe. So if we’d just parsed “1” we might end up with this:

> listToMaybe [(1, "")]
Just (1,"")

I only have a basic understanding of fmap yet because I’m not up to that chapter in ‘Learn Me A Haskell’.

As far as I know it’s used to apply a function to a value inside a container type object, i.e. the Maybe in this case, and then return the container type with its new value

In our case we start with ‘Just(1, “”)’ and we want to get to ‘Just 1’ so we use the ‘fst’ function to help us do that.

The main method of the program reads like this to wire it all together:

main = do args <- getArgs
          print (sum $ map (fromMaybe 0) $ (intify args))

I’m using fromMaybe to set a default value of 0 for any ‘Nothing’ values in the collection.

I compile the code like this:

> ghc --make learn_haskell_hard_way 
[1 of 1] Compiling Main             ( learn_haskell_hard_way.hs, learn_haskell_hard_way.o )
Linking learn_haskell_hard_way ...

And then run it:

>./learn_haskell_hard_way 1 2 3 4    

> ./learn_haskell_hard_way 1 2 3 4 mark says hello 5
  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket