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"] [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 [(1,"")]
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 10 > ./learn_haskell_hard_way 1 2 3 4 mark says hello 5 15