I was reviewing some code and came across the following gem, which I'd wager is a copy-paste of pointfree
output:
(I thought the following would more appropriate than the usual foo
/bar
for this particular question :P)
import Control.Monad (liftM2)
data Battleship = Battleship { x :: Int
, y :: Int
} deriving Show
placeBattleship :: Int -> Int -> Battleship
placeBattleship x' y' = Battleship { x = x', y = y' }
coordinates :: Battleship -> (Int, Int)
coordinates = liftM2 (,) x y
Would someone be kind enough to explain the steps needed to simplify from:
(i) coordinates b = (x b, y b)
to:
(ii) coordinates = liftM2 (,) x y
?
In particular, I'm a bit confused as to the use of liftM2
as I wasn't even aware that a monad was lurking in the background.
I know that (i) can also be represented as: coordinates s = (,) (x s) (y s)
but I'm not sure where/how to proceed.
P.S. The following is why I suspect it's from pointfree
(output is from GHCI
and :pl
is aliased to pointfree
):
λ: :pl coordinates s = (x s, y s)
coordinates = liftM2 (,) x y
This takes advantage of the
Monad
instance for(->) r
, also called the "reader monad". This is the monad of functions from a specific type toa
. (Take a look here for motivation on why it exists in the first place.)To see how it works for various functions, replace
m
with(r ->
inm a
. For example, if we just doliftM
, we get:...which is just function composition. Neat.
We can do the same thing for
liftM2
:So what we see is a way to compose two one-argument functions with a two-argument function. It's a way of generalizing normal function composition to more than one argument. The idea is that we create a function that takes a single
r
by passing that through both of the one-argument functions, getting two arguments to pass into the two-argument function. So if we havef :: (r -> a)
,g :: (r -> b)
andh :: (a -> b -> c)
, we produce:Now, how does this apply to your code?
(,)
is the two-argument function, andx
andy
are one-argument functions of the typeBattleship -> Int
(because that's how field accessors work). With this in mind:Once you've internalized the idea of multiple function composition like this, point-free code like this becomes quite a bit more readable—no need to use the pointfree tool! In this case, I think the non-pointfree version is still better, but the pointfree one isn't terrible itself.