Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What can Arrows do that Monads can't?

Tags:

Arrows seem to be gaining popularity in the Haskell community, but it seems to me like Monads are more powerful. What is gained by using Arrows? Why can't Monads be used instead?

like image 911
Vlad the Impala Avatar asked Apr 22 '13 19:04

Vlad the Impala


People also ask

What does -> mean in Haskell?

(->) is often called the "function arrow" or "function type constructor", and while it does have some special syntax, there's not that much special about it. It's essentially an infix type operator. Give it two types, and it gives you the type of functions between those types.

What does Arrow do in Haskell?

The Arrow (either (->) or MyArr ) is an abstraction of a computation. For a function b -> c , b is the input and c is the output. For a MyArr b c , b is the input and c is the output.

What is Kleisli arrow?

Kleisli is a type of Arrow for a Monadic context. It is defined as: final case class Kleisli[F[_], A, B](run: A => F[B]) Kleisli Type Signature. The Kleisli type is a wrapper around A => F[B] , where F is some context that is a Monad.

What are monads Haskell?

What is a Monad? 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.


2 Answers

Every monad gives rise to an arrow

newtype Kleisli m a b = Kleisli (a -> m b) instance Monad m => Category (Kleisli m) where    id = Kleisli return    (Kleisli f) . (Kleisli g) = Kleisli (\x -> (g x) >>= f) instance Monad m => Arrow (Kleisli m) where    arr f = Kleisli (return . f)    first (Kleisli f) = Kleisli (\(a,b) -> (f a) >>= \fa -> return (fa,b)) 

But, there are arrows which are not monads. Thus, there are arrows which do things that you can't do with monads. A good example is the arrow transformer to add some static information

data StaticT m c a b = StaticT m (c a b) instance (Category c, Monoid m) => Category (StaticT m c) where    id = StaticT mempty id    (StaticT m1 f) . (StaticT m2 g) = StaticT (m1 <> m2) (f . g) instance (Arrow c, Monoid m) => Arrow (StaticT m c) where    arr f = StaticT mempty (arr f)    first (StaticT m f) = StaticT m (first f) 

this arrow tranformer is usefull because it can be used to keep track of static properties of a program. For example, you can use this to instrument your API to statically measure how many calls you are making.

like image 50
Philip JF Avatar answered Nov 09 '22 23:11

Philip JF


I've always found it difficult to think of the issue in these terms: what is gained by using arrows. As other commenters have mentioned, every monad can trivially be turned into an arrow. So a monad can do all the arrow-y things. However, we can make Arrows that are not monads. That is to say, we can make types that can do these arrow-y things without making them support monadic binding. It might not seem like the case, but the monadic bind function is actually a pretty restrictive (hence powerful) operation that disqualifies many types.

See, to support bind, you have to be able to assert that that regardless of the input type, what's going to come out is going to be wrapped in the monad.

(>>=) :: forall a b. m a -> (a -> m b) -> m b 

But, how would we define bind for a type like data Foo a = F Bool a Surely, we could combine one Foo's a with another's but how would we combine the Bools. Imagine that the Bool marked, say, whether or not the value of the other parameter had changed. If I have a = Foo False whatever and I bind it into a function, I have no idea whether or not that function is going to change whatever. I can't write a bind that correctly sets the Bool. This is often called the problem of static meta-information. I cannot inspect the function being bound into to determine whether or not it will alter whatever.

There are several other cases like this: types that represent mutating functions, parsers that can exit early, etc. But the basic idea is this: monads set a high bar that not all types can clear. Arrows allow you to compose types (that may or may not be able to support this high, binding standard) in powerful ways without having to satisfy bind. Of course, you do lose some of the power of monads.

Moral of the story: there's nothing an arrow can do that monad cannot, because a monad can always be made into an arrow. However, sometimes you can't make your types into monads but you still want to allow them to have most of the compositional flexibility and power of monads.

Many of these ideas were inspired by the superb Understanding Haskell Arrows (backup)

like image 22
Erik Hinton Avatar answered Nov 09 '22 23:11

Erik Hinton