haskellfunctional-programmingpointfree

Understanding `ap` in a point-free function in Haskell


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?


Solution

  • When the monad m is (->) a, as in your case, you can define ap as follows:

    ap f g = \x -> f x (g x)
    

    We can see that this indeed "works" in your pointfree example.

    average = ap ((/) . realToFrac . sum) genericLength
    average = \x -> ((/) . realToFrac . sum) x (genericLength x)
    average = \x -> (/) (realToFrac (sum x)) (genericLength x)
    average = \x -> realToFrac (sum x) / genericLength x
    

    We can also derive ap from the general law

    ap f g = do ff <- f ; gg <- g ; return (ff gg)
    

    that is, desugaring the do-notation

    ap f g = f >>= \ff -> g >>= \gg -> return (ff gg)
    

    If we substitute the definitions of the monad methods

    m >>= f = \x -> f (m x) x
    return x = \_ -> x
    

    we get the previous definition of ap back (for our specific monad (->) a). Indeed:

    app f g 
    = f >>= \ff -> g >>= \gg -> return (ff gg)
    = f >>= \ff -> g >>= \gg -> \_ -> ff gg
    = f >>= \ff -> g >>= \gg _ -> ff gg
    = f >>= \ff -> \x -> (\gg _ -> ff gg) (g x) x
    = f >>= \ff -> \x -> (\_ -> ff (g x)) x
    = f >>= \ff -> \x -> ff (g x)
    = f >>= \ff x -> ff (g x)
    = \y -> (\ff x -> ff (g x)) (f y) y
    = \y -> (\x -> f y (g x)) y
    = \y -> f y (g y)