Search code examples
haskellfunction-compositionpointfree

Confusion about function composition in Haskell


Consider following function definition in ghci.

let myF = sin . cos . sum 

where, . stands for composition of two function (right associative). This I can call

myF [3.14, 3.14]

and it gives me desired result. Apparently, it passes list [3.14, 3.14] to function 'sum' and its 'result' is passed to cos and so on and on. However, if I do this in interpreter

let myF y = sin . cos . sum y 

or

let myF y = sin . cos (sum y) 

then I run into troubles. Modifying this into following gives me desired result.

let myF y = sin . cos $ sum y 

or

let myF y = sin . cos . sum $ y 

The type of (.) suggests that there should not be a problem with following form since 'sum y' is also a function (isn't it? After-all everything is a function in Haskell?)

let myF y = sin . cos . sum y -- this should work?

What is more interesting that I can make it work with two (or many) arguments (think of passing list [3.14, 3.14] as two arguments x and y), I have to write the following

let (myF x) y = (sin . cos . (+ x)) y 
myF 3.14 3.14 -- it works! 
let myF = sin . cos . (+) 
myF 3.14 3.14 -- -- Doesn't work!

There is some discussion on HaskellWiki regarding this form which they call 'PointFree' form http://www.haskell.org/haskellwiki/Pointfree . By reading this article, I am suspecting that this form is different from composition of two lambda expressions. I am getting confused when I try to draw a line separating both of these styles.


Solution

  • Let's look at the types. For sin and cos we have:

    cos, sin :: Floating a => a -> a
    

    For sum:

    sum :: Num a => [a] -> a
    

    Now, sum y turns that into a

    sum y :: Num a => a
    

    which is a value, not a function (you could name it a function with no arguments but this is very tricky and you also need to name () -> a functions - there was a discussion somewhere about this but I cannot find the link now - Conal spoke about it).

    Anyway, trying cos . sum y won't work because . expects both sides to have types a -> b and b -> c (signature is (b -> c) -> (a -> b) -> (a -> c)) and sum y cannot be written in this style. That's why you need to include parentheses or $.

    As for point-free style, the simples translation recipe is this:

    • take you function and move the last argument of function to the end of the expression separated by a function application. For example, in case of mysum x y = x + y we have y at the end but we cannot remove it right now. Instead, rewriting as mysum x y = (x +) y it works.
    • remove said argument. In our case mysum x = (x +)
    • repeat until you have no more arguments. Here mysum = (+)

    (I chose a simple example, for more convoluted cases you'll have to use flip and others)