Search code examples
haskelloperator-precedencecompositionfunction-composition

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!


Solution

  • 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.