11

Diving into Haskell, and while I am enjoying the language I'm finding the pointfree style completely illegible. I've come a across this function which only consists of these ASCII boobies as seen below.

f = (.)(.)

And while I understand its type signature and what it does, I can't for the life of me understand why it does it. So could someone please write out the de-pointfreed version of it for me, and maybe step by step work back to the pointfree version sorta like this:

f g x y = (g x) + y   
f g x = (+) (g x)    
f g = (+) . g    
f = (.) (+)
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Erik
  • 293
  • 1
  • 8

3 Answers3

18

Generally (?) (where ? stands for an arbitrary infix operator) is the same as \x y -> x ? y. So we can rewrite f as:

f = (\a b -> a . b) (\c d -> c . d)

Now if we apply the argument to the function, we get:

f = (\b -> (\c d -> c . d) . b)

Now b is just an argument to f, so we can rewrite this as:

f b = (\c d -> c . d) . b

The definition of . is f . g = \x -> f (g x). If replace the outer . with its definition, we get:

f b = \x -> (\c d -> c . d) (b x)

Again we can turn x into a regular parameter:

f b x = (\c d -> c . d) (b x)

Now let's replace the other .:

f b x = (\c d y -> c (d y)) (b x)

Now let's apply the argument:

f b x = \d y -> (b x) (d y)

Now let's move the parameters again:

f b x d y = (b x) (d y)

Done.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • Why does the `(\c d -> c . d)` replace the `a` and not the `b`? I though Haskell went from right to left. – Erik May 21 '18 at 16:31
  • 2
    @Erik `\a b -> ...` is short for `\a -> (\b -> ...)`. So you have a function that, when given an argument `a`, produces another function expecting an argument `b`. So `a` comes first. Or put differently: given `(\a b -> ...) 1 2`, `a` will be `1` and `b` will be `2`. So the left-most argument goes to the left-most parameter. – sepp2k May 21 '18 at 16:34
5

We can work backwards by "pattern matching" over the combinators' definitions. Given

f a b c d =  a b  (c d) 
          = (a b) (c d)

we proceed

         = B (a b) c d 
         = B B a b c d    -- writing B for (.)

so by eta-contraction

f = B B 

because

a (b c) = B a b c         -- bidirectional equation

by definition. Haskell's (.) is actually the B combinator (see BCKW combinators).


edit: Potentially, many combinators can match the same code. That's why there are many possible combinatory encodings for the same piece of code. For example, (ab)(cd) = (ab)(I(cd)) is a valid transformation, which might lead to some other combinator definition matching that. Choosing the "most appropriate" one is an art (or a search in a search space with somewhat high branching factor).

That's about going backwards, as you asked. But if you want to go "forward", personally, I like the combinatory approach much better over the lambda notation fidgeting. I would even just write many arguments right away, and get rid of the extra ones in the end:

BBabcdefg = B(ab)cdefg = (ab)(cd)efg 

hence,

BBabcd    = B(ab)cd    = (ab)(cd) 

is all there is to it.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • I think this could really use some more background. It almost looks like it's written backwards. – dfeuer May 21 '18 at 21:58
  • 1
    @dfeuer well, the OP asked for "backwards"... seriously though, I've realized this after writing it, but decided it was short enough for a reader to follow (or equivalently, read it bottom to top). I also assumed/remembered that "eta-contraction" is easily googlable. what more background could it be? – Will Ness May 22 '18 at 06:52
  • What do you mean by "'pattern matching' over the combinator definitions"? That's what really threw me for a loop. – dfeuer May 22 '18 at 06:56
  • 1
    @dfeuer I've edited this to the asnwer, thanks for the discussion. – Will Ness May 22 '18 at 07:12
5

You can also gradually append arguments to f:

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

The result reveals that x and z are actually (binary and unary, respectively) functions, so I'll use different identifiers:

f g x h y = g x (h y)
fghzxm
  • 1,185
  • 7
  • 19