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...
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.
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.
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”);
The Maybe sum type is a useful data type that forms a functor. Like many other useful functors, it also forms a monad.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With