MathematicalOrchid
MathematicalOrchid

Reputation: 62818

Making monadic code shorter

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

Answers (3)

MathematicalOrchid
MathematicalOrchid

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

Tarmil
Tarmil

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

Cubic
Cubic

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

Related Questions