Search code examples
haskellcompositionhigher-order-functions

How swedish is a very very swedish greeting?


Consider the following Haskell definitions, taken from this excellent Haskell video on YouTube:

import Data.List
greeting = "Hello"
swedish = intersperse 'f'
very f x = f (f (f x))

If we load them into GHCi, we see the following results:

ghci> swedish greeting
"Hfeflflfo"
ghci> very swedish greeting
"Hfffffffeffffffflffffffflfffffffo"
ghci> very very swedish greeting
"Hffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
fffffffffffffffffffffffffffff... (536,870,913 chars total)

The first two outputs I understand perfectly. A swedish greeting comes out interspersed with fs, and a very swedish greeting is just a swedish (swedish (swedish greeting)), which comes out triply interspersed.

But what exactly is happening in the third input line? My (rather incomplete) understanding of Haskell syntax says that a space-separated sequence of expressions is interpreted as a function call, where the first expression is the function and the rest are the arguments. In that case, how is the outermost very being called with three arguments (very, swedish, and greeting) when it's only defined to accept two?

If it helps, it appears that a very very swedish greeting is equivalent to a swedish $ swedish $ swedish $ swedish $ ... (27 layers of swedish) ... $ swedish $ swedish greeting.


Solution

  • You said:

    My (rather incomplete) understanding of Haskell syntax says that a space-separated sequence of expressions is interpreted as a function call, where the first expression is the function and the rest are the arguments.

    You are correct that this is not a complete understanding of what is actually going on. From your example:

    very very swedish greeting
    

    This is the same as:

    ((very very) swedish) greeting
    

    This is because function application is left associative. Furthermore, every function in Haskell takes one input and returns one result. What you think of as functions accepting multiple inputs are, in reality, functions that accept a single input and return a function as their result.

    This also explains why arrows (->) delimit function inputs and results in function types. Consider the type of ++:

    (++) :: [a] -> [a] -> [a]
    

    This is the same as:

    (++) :: [a] -> ([a] -> [a])
    

    You may think of the ++ operator as taking two lists and returning a list, but in reality it is a function of one input (a list) that returns a function of one input (another list) that returns a list.

    Taken all together, you can hopefully see that very very is a valid expression by itself (and happens to have the same type as very).

    very very x
    

    is equivalent to:

    very (very (very x))