3
votes

I need some help understanding a Haskell template of the "List Replication" Hackerrank challenge. I do not understand the order in which the functions are executed.

f = concatMap . replicate

-- This part handles the Input and Output and can be used as it is. 
main :: IO ()
main = getContents >>=
       mapM_ print . (\(n:arr) -> f n arr) . map read . words

The getContents function should produce an IO String like "2\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10". I understand roughly what happens next, but I do not understand in what order and with which precedence. I tried to execute words "2\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10 in ghci and to feed the result into map read. But then I get the result "[*** Exception: Prelude.read: no parse". If I try map read . words "2\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" I get the result "Couldn't match expected type ‘a -> [String]’ with actual type ‘[String]’". How is it that the whole composed main function throws no errors?

I would very much appreciate if someone could help me understand the whole main function. Especially which functions get executed in which order with which input, and how I could slice it in smaller (working!) parts to understand it better.

Many thanks!

1
This is type inference at work. There are many instances of read and GHC can see what is the one you want from the context. GHCi, without the proper context, infers the wrong one and causes the crash. Inconvenient, but that's what it happened. - chi

1 Answers

4
votes

Defining at GHCi prompt

g = (\(n:arr) -> f n arr)
 where
 f = concatMap . replicate

we get the derived type back as g :: [Int] -> [Int]. This defines the type of read in the map read (feeding into g) as String -> Int, and everything works. It could be easier to follow in the more readable form,

g :: [Int] -> [Int]
g (n:arr) = concatMap (replicate n) arr

The type Int is derived for n :: Int because of replicate :: Int -> a -> [a], and the type for arr as well as (n:arr) :: [Int] follows from that because Haskell lists are homogeneous, i.e. having all the elements of the same type.

The most general type of read is read :: Read a => String -> a. Indeed Int is in Read type class.

But what if we don't know whether this is an Int, a Double, or something else? Then read does not know which format to parse, and so we get the "read: no parse" error message back.

This is what happens when we try

(map read . words) "2\n3\n4\n"  =  map read $ words "2\n3\n4\n" 
                                =  map read (words "2\n3\n4\n")

The $ appears there instead of the . when we open the parentheses. Due to the operator precedence your main function is actually

main :: IO ()
main = getContents >>=
       (mapM_ print . (\(n:arr) -> f n arr) . map read . words)
     = getContents >>= (\s -> 
       (mapM_ print . g                     . map read . words) s)
     = getContents >>= (\s -> 
        mapM_ print $ g                     $ map read $ words  s)

Using . instead of $ there as you did is wrong, because just omitting those parentheses is wrong, causes that other error message related to functions, as . expects a function as its argument on the right but ["2","3","4"] is not a function.