In the comments of the question Tacit function composition in Haskell, people mentioned making a Num instance for a -> r, so I thought I’d play with using function notation to represent multiplication:
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative
instance Show (a->r) where -- not needed in recent GHC versions
show f = " a function "
instance Eq (a->r) where -- not needed in recent GHC versions
f == g = error "sorry, Haskell, I lied, I can't really compare functions for equality"
instance (Num r,a~r) => Num (a -> r) where
(+) = liftA2 (+)
(-) = liftA2 (-)
(*) = liftA2 (*)
abs = liftA abs
negate = liftA negate
signum = liftA signum
fromInteger a = (fromInteger a *)
Note that the fromInteger definition means I can write 3 4 which evaluates to 12, and 7 (2+8) is 70, just as you’d hope.
Then it all goes wonderfully, entertainingly weird! Please explain this wierdness if you can:
*Main> 1 2 3
18
*Main> 1 2 4
32
*Main> 1 2 5
50
*Main> 2 2 3
36
*Main> 2 2 4
64
*Main> 2 2 5
100
*Main> (2 3) (5 2)
600
[Edit: used Applicative instead of Monad because Applicative is great generally, but it doesn’t make much difference at all to the code.]
In an expression like
2 3 4with your instances, both2and3are functions. So2is actually(2 *)and has a typeNum a => a -> a.3is the same.2 3is then(2 *) (3 *)which is the same as2 * (3 *). By your instance, this isliftM2 (*) 2 (3 *)which is thenliftM2 (*) (2 *) (3 *). Now this expression works without any of your instances.So what does this mean? Well,
liftM2for functions is a sort of double composition. In particular,liftM2 f g his the same as\ x -> f (g x) (h x). SoliftM2 (*) (2 *) (3 *)is then\ x -> (*) ((2 *) x) ((3 *) x). Simplifying a bit, we get:\ x -> (2 * x) * (3 * x). So now we know that2 3 4is actually(2 * 4) * (3 * 4).Now then, why does
liftM2for functions work this way? Let’s look at the monad instance for(->) r(keep in mind that(->) ris(r ->)but we can’t write type-level operator sections):So
returnisconst.>>=is a little weird. I think it’s easier to see this in terms ofjoin. For functions,joinworks like this:That is, it takes a function of two arguments and turns it into a function of one argument by using that argument twice. Simple enough. This definition also makes sense. For functions,
joinhas to turn a function of two arguments into a function of one; the only reasonable way to do this is to use that one argument twice.>>=isfmapfollowed byjoin. For functions,fmapis just(.). So now>>=is equal to:which is just:
now we just get rid of
.to get:So now that we know how
>>=works, we can look atliftM2.liftM2is defined as follows:We can simply this bit by bit. First,
return (f a' b')turns into\ _ -> f a' b'. Combined with the\ b' ->, we get:\ b' _ -> f a' b'. Thenb >>= \ b' _ -> f a' b'turns into:since the second
xis ignored, we get:\ x -> (\ b' -> f a' b') (b x)which is then reduced to\ x -> f a' (b x). So this leaves us with:Again, we substitute
>>=:this reduces to:
which is exactly what we used as
liftM2earlier!Hopefully now the behavior of
2 3 4makes sense completely.