I am able to understand the basics of point-free functions in Haskell:
addOne x = 1 + x
As we see x on both sides of the equation, we simplify it:
addOne = (+ 1)
Incredibly it turns out that functions where the same argument is used twice in different parts can be written point-free!
Let me take as a basic example the average
function written as:
average xs = realToFrac (sum xs) / genericLength xs
It may seem impossible to simplify xs
, but http://pointfree.io/ comes out with:
average = ap ((/) . realToFrac . sum) genericLength
That works.
As far as I understand this states that average
is the same as calling ap
on two functions, the composition of (/) . realToFrac . sum
and genericLength
Unfortunately the ap
function makes no sense whatsoever to me, the docs http://hackage.haskell.org/package/base-4.8.1.0/docs/Control-Monad.html#v:ap state:
ap :: Monad m => m (a -> b) -> m a -> m b
In many situations, the liftM operations can be replaced by uses of ap,
which promotes function application.
return f `ap` x1 `ap` ... `ap` xn
is equivalent to
liftMn f x1 x2 ... xn
But writing:
let average = liftM2 ((/) . realToFrac . sum) genericLength
does not work, (gives a very long type error message, ask and I'll include it), so I do not understand what the docs are trying to say.
How does the expression ap ((/) . realToFrac . sum) genericLength
work? Could you explain ap
in simpler terms than the docs?
Any lambda term can be rewritten to an equivalent term that uses just a set of suitable combinators and no lambda abstractions. This process is called abstraciton elimination. During the process you want to remove lambda abstractions from inside out. So at one step you have
λx.M
whereM
is already free of lambda abstractions, and you want to get rid ofx
.M
isx
, you replaceλx.x
withid
(id
is usually denoted byI
in combinatory logic).M
doesn't containx
, you replace the term withconst M
(const
is usually denoted byK
in combinatory logic).If
M
isPQ
, that is the term isλx.PQ
, you want to "push"x
inside both parts of the function application so that you can recursively process both parts. This is accomplished by using theS
combinator defined asλfgx.(fx)(gx)
, that is, it takes two functions and passesx
to both of them, and applies the results together. You can easily verify that thatλx.PQ
is equivalent toS(λx.P)(λx.Q)
, and we can recursively process both subterms.As described in the other answers, the
S
combinator is available in Haskell asap
(or<*>
) specialized to the reader monad.The appearance of the reader monad isn't accidental: When solving the task of replacing
λx.M
with an equivalent function is basically liftingM :: a
to the reader monadr -> a
(actually the reader Applicative part is enough), wherer
is the type ofx
. If we revise the process above:M
isx
. Then we "lift"x
toid
, to get rid of the variable. The other cases below are just mechanical applications of lifting an expression to an applicative functor:λx.M
whereM
doesn't containx
, it's just liftingM
to the reader applicative, which ispure M
. Indeed, for(->) r
,pure
is equivalent toconst
.<*> :: f (a -> b) -> f a -> f b
is function application lifted to a monad/applicative. And this is exactly what we do: We lift both partsP
andQ
to the reader applicative and then use<*>
to bind them together.The process can be further improved by adding more combinators, which allows the resulting term to be shorter. Most often, combinators
B
andC
are used, which in Haskell correspond to functions(.)
andflip
. And again,(.)
is justfmap
/<$>
for the reader applicative. (I'm not aware of such a built-in function for expressingflip
, but it'd be viewed as a specialization off (a -> b) -> a -> f b
for the reader applicative.)Some time ago I wrote a short article about this: The Monad Reader Issue 17, The Reader Monad and Abstraction Elimination.
When the monad
m
is(->) a
, as in your case, you can defineap
as follows:We can see that this indeed "works" in your pointfree example.
We can also derive
ap
from the general lawthat is, desugaring the
do
-notationIf we substitute the definitions of the monad methods
we get the previous definition of
ap
back (for our specific monad(->) a
). Indeed: