Lambdabot is an excellent tool for learning point-free style in Haskell and related languages. When asked to point-free an expression, it often makes heavy use of sections of (.). I used to have a hard time reading expressions built out of those things, and at first I assumed it was just a Lambdabot quirk that nobody really used in their own code. Soon enough I was proven wrong. I kept seeing them in real code written by people who were very much not bots, so after failing to develop any intuition for them on my own, I asked for help on #haskell.

Here is the conversation in full.

[18:35] <rahul_> Hi
[18:37] <rahul_> I have noticed this general pattern while composing functions. `f x e = a (b x e)` can be written as `f = (a .) . b`, and `f x e = a x (b e)` can be written as `f = (. b) . a`
[18:37] < dankna > yes, indeed
[18:38] <rahul_> I however fail to see how to intuitively think of these compositions. I have almost had them memorized now, but I didn't come up with these on my own. They got etched into my memory after repeated experiments with lambdabot.
[18:38] <rahul_> Can someone please tell me how I should "read" these point-free versions?
[18:38] <rahul_> Sorry if my question is vague. Dunno how else to put it. :-(
[18:39] < dankna > I don't know that they necessarily have an intuitive translation into English
[18:39] < dankna > which I think is what you're asking for
[18:39] <rahul_> dankna: Yes.
[18:39] < dainanaki > Basically you read them by putting together the type signatures in your head.
[18:39] < RaptorRarr > rahul_: You mean (a .) and (. a)?
[18:39] < dankna > I guess I'd say f = (a .) . b
[18:39] < dankna > as
[18:40] <rahul_> RaptorRarr: Yes.
[18:40] < Botje > rahul_: if you expand them once, you get f x = a . b x back
[18:40] <rahul_> dainanaki: Tried that. It blows my head. :(
[18:40] < dankna > "A slice-compose, composed with B"
[18:40] < dainanaki > It takes time.
[18:40] < dankna > and f = (. a) . b as
[18:40] < dankna > "Slice-compose A, composed with B"
[18:40] < dankna > but that convention is not very useful
[18:40] < dainanaki > rahul_: my advice is trying them out on paper until you get used to it.
[18:40] < dankna > in particular it doesn't generalize to things with more levels of parentheses
[18:40] < RaptorRarr > rahul_: "Apply b, then (a .). (a .) takes the first argument as if it's a function."
[18:40] < dankna > you really want to get to a point where "math is your first language"
[18:40] < dankna > that is
[18:41] < moriramar > @pl f x = g x (d c x)
[18:41] < lambdabot > f = ap g (d c)
[18:41] < RaptorRarr > @unpl (a .) . b
[18:41] < lambdabot > (\ f i -> a (b f i))
[18:41] < int-e > Heh. I just don't use sections of (.).
[18:41] < dankna > it's a standard test of natural-language proficiency that you can do arithmetic, speaking the intermediate steps out loud, as quickly in the target language as you can in your first language
[18:41] < dankna > but for me and probably many people here, I can't even do that in English!
[18:41] < dankna > because it's faster to treat the math as "its own mental object"
[18:41] < dankna > and not put it in words at all
[18:42] <rahul_> I understood arrows after I read Wikibook's arrows chapter that provides a metaphor of conveyor belt and robots. Something like that would help!
[18:42] < Botje > rahul_: there is no metaphor. only zool^w the source
[18:42] < dankna > sp. zuul
[18:42] <rahul_> dankna, Botje : Sounds good. Maybe I should be patient, and wait for my moment of mathematical enlightenment. :-)
[18:42] < dankna > rahul_: yeah :)
[18:43] < benmachine > I sort of imagine squashing arguments on the end with sufficient force that a dot pops out, then clamping it there with some parentheses
[18:43] < dankna > benmachine: hahhahahaa! wow
[18:43] < benmachine > and then sorting out the sections
[18:43] <rahul_> benmachine: LOL
[18:43] < benmachine > :P
[18:44] < RaptorRarr > @unpl (a .)
[18:44] < lambdabot > (\ b e -> a (b e))
[18:44] < RaptorRarr > @unpl (. a)
[18:44] < lambdabot > (\ b e -> b (a e))

I still was not satisfied with the answers. Then yesterday I stumbled across the topic again, tweeted about it, and thankfully @runarorama replied with an explanation that finally helped:

(f .) is a function that modifies the return type of another function by f. (. f) is a function that modifies the argument type of another function by f.

That is a very good way to think about expressions that use sections of (.). It feels obvious once someone tells you, but it really was not obvious to me beforehand.

Even after hearing that explanation, I still do not reach for sections of (.) naturally. Back in October I also tried to come up with simpler combinators that captured common (.) patterns in more meaningful ways, but I never got very far. For now, I have mostly decided not to use sections of (.) in my own code, and to expand them mentally whenever I run into them in somebody else’s.

Incidentally, I also came across the Generic.Pointless.Combinators library this morning. It looks interesting, and I’m looking forward to playing with it this weekend.

I’ll close this short post with a hat tip to concatenative languages, where composition like this is a no-brainer. In Factor, both \x e -> a (b x e) and \x e -> a x (b e) become simply b a.