1

I'm having trouble understanding how the function composition operator works in Haskell. I understand it in simple cases where you only use functions that takes one argument but I don't understand whats going on when you use it on several functions that each take more than one argument.

Consider this function:

f1 = (*) . (+) 2 $ 1

All this function does is that it takes a number and multiplies it with three so it's the same as:

f2 = (*) $ (+) 2 1

and

f3 = (*) 3

I understand exactly whats going on in f2 but I dont get what is going on in f1 that makes the two the same. The (.) takes two functions and a value, each of the functions can only take one argument so in this case both (*) and (+) has to be partially applied for things to work. What I don't understand is what they are applied to and how this is sent through the chain of functions.

In f2 (+) is first applied to both funtions producing a value that is partially applied to (*), creating a function. In f1 it is not possible for (+) to be assigned to both values since (.) requires a function as input, yet they are the same. I do not understand this.

I hope you understand what I'm having problems understanding. Thanks in advance!

Alex
  • 731
  • 1
  • 11
  • 24
  • Join the club! Some folks are really adept at understanding these weird function compositions. Some of us not so much. I haven't found it to be a big obstacle in the long term, so I would just not worry about this too much... – Luis Casillas Dec 04 '14 at 20:02
  • 1
    I actually don't care that much, my professor does ;) – Alex Dec 04 '14 at 20:09

4 Answers4

3

Looking at the operator precedence might help:

f1 = ((*) . ((+) 2)) $ 1

In other words, it’s (*) of (2+) – that is, \x -> (*) ((2+) x) – on 1, making it (*) ((2+) 1), which is (*) (2 + 1), which is (*) 3, and that’s f2.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Ry-
  • 218,210
  • 55
  • 464
  • 476
3

(.) is just another function in Haskell:

(.) f g x = f (g x)

both f and g may expect more than one arguments, by the way. This means that

(f . g) = (.) f g = (\x -> f (g x))         -- !!

This is the key: any (last) argument on left hand side of a Haskell equational definition can go to the right hand side, where it becomes a (first) lambda parameter.

So,

f1 = (*) . (+) 2 $ 1
   = ((*) . ((+) 2)) 1            -- this is how it is really read
   = (\x -> (*) ( ((+) 2) x )) 1
   =        (*) ( ((+) 2) 1 )
   =        (*) (  (+) 2  1 )
   =        (*) 3

No mysteries there, really.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
2

As @minitech points out, first understand the order of operations, i.e.:

f1 = ((*) . ((+) 2)) $ 1

In general, for binary operations f and g, this equality is true:

((f . (g x)) $ y) z = f (g x y) z

We just use the definition of the compose operator: (f . g) a = f (g a).

 (f . (g x)) $ y = (f . (g x)) y = f (g x y)

and so:

 ((f . (g x)) $ y) z = f (g x y) z

Now substitute f = (*), g = (+), x = 2 and y = 1.

ErikR
  • 51,541
  • 9
  • 73
  • 124
1

In terms of order of operations, Haskell generally goes: Parentheses, Function Applications, Operators, Special Forms. So fact 8 + case h (f 3 + g 1) of Nothing -> 2 will parse as:

(+)
    fact
        8
    case
        h
            (+)
                f
                    3
                g
                    1
            Nothing
            2

with both the f and g happening first. In terms of multiple functions, f g h is implicitly (f g) h -- this is called a "left-associative" law, but I like to call it "the law of greedy nom": a function consumes ("noms") whatever single thing is immediately in front of it without waiting and is satisfied. (Well -- it may produce a function which takes another argument, of course...)

Your two functions' parse trees are:

f1 = (*) . (+) 2 $ 1
($)
    (.)
        (*)
        (+)
            2
    1

f2 = (*) $ (+) 2 1
($)
    (*)
    (+)
        2
        1

We of course know that a $ b = a b is the definition of $ and a . b = \x -> a (b x) is the definition of .. From this we see the deep identity that:

(a . b) c == a $ b c

(the $ function can be thought of as "lazy nom", I am saying that a will consume the entire rest of the expression as its argument.)

In other words, function application converts a . into a $. That is actually all that you need to understand the above expression:

(*) . (+) 2 $ 1
--> ((*) . (+) 2) 1  [use explicit () instead of the precedence of $]
--> (*) $ (+) 2 1  [your expression]

Intuitively, the (*) . (2 +) above says "take the output of (2 +) and feed it into the input of (*). If you feed 4 to the result, the first thing which happens is that it computes 2 + 4, outputting 6, and then this is fed into the input of (*) to make (6 *).

CR Drost
  • 9,637
  • 1
  • 25
  • 36