Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making monadic code shorter

Tags:

haskell

monads

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...

like image 821
MathematicalOrchid Avatar asked Feb 15 '14 13:02

MathematicalOrchid


People also ask

Is reduce a monad?

Reduce ( / , ⌿ ), also called Reduction or Insert, is a primitive monadic operator which takes a dyadic function operand, inserts it between the elements of the argument, and evaluates it into a single array in right-to-left order.

What is a monad in simple terms?

A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.

What is a monadic computation?

Idea. In computer science, a monad describes a “notion of computation”. Formally, it is a map that. sends every type X of some given programming language to a new type T(X) (called the “type of T-computations with values in X”);

Is maybe a monad?

The Maybe sum type is a useful data type that forms a functor. Like many other useful functors, it also forms a monad.


1 Answers

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
like image 171
Tarmil Avatar answered Oct 02 '22 12:10

Tarmil