niklasd
niklasd

Reputation: 65

Explanation about Haskell operator precedence and function composition

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!

Upvotes: 3

Views: 175

Answers (1)

Will Ness
Will Ness

Reputation: 71119

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.

Upvotes: 4

Related Questions