Reputation: 62818
Consider the following code:
transform :: Foo -> Bar
transform foo =
case foo of
Foo1 x -> Foo1 x
Foo2 x y -> Foo2 x (transform y)
Foo3 x y z -> Foo3 x (transform y) (transform z)
Now suppose for some reason I change this to work in a monad (e.g., because I have state I want to carry around or whatever). Now we have
transform :: Foo -> State Int Bar
transform foo =
case foo of
Foo1 x -> return $ Foo1 x
Foo2 x y -> do
y' <- transform y
return $ Foo2 x y'
Foo3 x y z -> do
y' <- transform y
z' <- transform z
return $ Foo3 x y' z'
Well that all works and everything, but... can we improve this? I have a nagging feeling that I ought to be able to define some nifty infix function to make this look nicer, but every time I try to work out how, my mind goes numb after a while...
Upvotes: 9
Views: 227
Reputation: 62818
For anybody else trying to figure this out...
It seems the crucial definitions are these:
mf <*> mx = do
f <- mf
x <- mx
return (f x)
f <$> mx = do
x <- mx
return (f x)
In particular, the types are different; <*>
takes mf
while <$>
takes f
:
(<*>) :: Monad m => m (x -> y) -> m x -> m y
(<$>) :: Monad m => (x -> y) -> m x -> m y
(Not, of course, that either of these are actually Monad
methods, or even methods at all. But you get the idea...)
As somebody who never uses fmap
, this takes a while to get used to...
Upvotes: 0
Reputation: 11362
Your intuition is right. This is the role of the ap
function in the Monad
class, or equivalently of the <*>
operator in the Applicative
class, which pretty much all monads implement (and will actually become a superclass of Monad
in the future).
Here is its type:
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
So it basically applies a wrapped function a -> b
to a wrapped a
to return a wrapped b
. It is equivalent to:
mf <*> mx = do
f <- mf
x <- mx
return $ f x
Here is how to use it in your case, emphasizing the similarity between the different cases:
transform foo =
case foo of
Foo1 x -> return Foo1 <*> return x
Foo2 x y -> return Foo2 <*> return x <*> transform y
Foo3 x y z -> return Foo3 <*> return x <*> transform y <*> transform z
This can be shortened by considering that return f <*> return x == return (f x)
:
transform foo =
case foo of
Foo1 x -> return $ Foo1 x
Foo2 x y -> return (Foo2 x) <*> transform y
Foo3 x y z -> return (Foo3 x) <*> transform y <*> transform z
And even further, by using the operator <$>
which is equivalent to fmap
from the Functor
class:
transform foo =
case foo of
Foo1 x -> return $ Foo1 x
Foo2 x y -> Foo2 x <$> transform y
Foo3 x y z -> Foo3 x <$> transform y <*> transform z
Upvotes: 10
Reputation: 15673
transform :: Foo -> State Int Bar
transform foo =
case foo of
Foo1 x -> return $ Foo1 x
Foo2 x y -> Foo2 x <$> transform y
Foo3 x y z -> Foo3 x <$> transform y <*> transform z
Requires Control.Applicative
and Functor
/Applicative
instances for your Monad (they're there for state, and relatively trivial to implement for other Monads).
Upvotes: 2