
On 10/12/10 5:56 AM, Uwe Schmidt wrote:
Hi Gregory,
As I understood, John Hughes invented the arrows as a generalisation of monads, you say it's a less powerful concept. I'm a bit puzzled with that. Could you explain these different views.
Consider the following example: f :: Int -> m a f i = monads !! (i *5 `mod` length monads) where monads = [...] :: [m a] In this case, we see that the action chosen by f is a non-trivial function of the input argument. In general, there is no way that we can represent this equivalently as an arrow. The reason why is because, unlike monads, in general arrows do not give you arbitrary freedom to choose the action performed by the arrow. It is true that one can get this freedom back for arrows which are instances of ArrowApply, but that that point what you really have is a monad with different notation. Hughes himself said that when your arrow is an instance of ArrowApply, you are better off just sticking with monads. The reason for the existence of arrows is because allowing the user to arbitrarily choose actions restricts the power that the creator of a library has to do stuff behind the scenes with these actions. To see why, suppose we are chaining together two arrows like so: f :: Arrow a b g :: Arrow b c h :: Arrow a c h = f >>> g When defining the composition operator >>>, the library author has the power to "open up" both of the actions f and g, which gives him lots of power in defining what h should be. By contrast, suppose we are chaining together two monads like this: f :: Monad b g :: b -> Monad c h :: Monad c h = f >>= g When defining the composition operator >>=, the library author can look inside f, but he has no way of knowing which action will be chosen by g, so he cannot use this information to decide what h should be. The motivation Hughes gave for arrows was parsers that have a "static" part in them; he pointed out that using monads we have no way to access and combine these static parts, so we needed to come up with a formalism that let us have a nice combinator framework like monads but which allows us to "break open" components so that we can combine them in interesting ways. So in short, arrows (relative to monads) take away some of the power of the user to choose actions as a function of the input in order to give library authors more power to define how actions are combined. In this sense arrows are "less powerful" from a user perspective than monads, except in the case where an arrow is an instance of ArrowApply in which case they are
... Yes, but the>=> operator lets you do the same thing with monads, and in fact I use it all the time to do point-free programming with monads, so this isn't at all an advantage that arrows have over monads. yes, I agree. What about the other combinators (&&&, ***,...)?
A couple of points in response. First, though it is less convenient, you can use those combinators if you wrap your arrow inside the Kleisli newtype wrapper, which is something I have occasionally done. Second, while I see your point that there are some combinators that arrows have that are not defined for monads, there are not that many of them, and it would be trivial to write special instances of them for monads; it seems to me that the price of making versions of these operators for monads is less than the price of having to re-implement all of the existing monad combinators and libraries using arrow notation just to not have to redefined monad versions of &&&, etc.
... No, that is not at all the problem with arrows. The problem with arrows is that they are more restrictive than monads in two respects. First, unlike monads, in general they do not let you perform an arbitrary action in response to an input. ... It's rather easy to define some choice combinators. Or am I missing the point?
No, in general arrows do not allow you to define choice combinators. Having said that, you *can* give the user some power to choose between fixed choices action in exchange for possibly losing power from your perspective a library author. If your arrow is an instance of ArrowChoice, then you give your user the ability to choose between a fixed set of predefined actions. However, the user is not able to compute an arbitrary action in response to an input. To get this power, you need to make your arrow an instance of ArrowApply, in which case you really aren't getting any benefit from the perspective of either a user *or* a library author over using a monad.
yes, this a common pattern, a function f' with an extra argument of type a, and sometimes you want to call f' with some value, e.g. f' 42, sometimes the extra argument must be computed from the arrow input, let's say with an arrow g.
For this case in hxt there is a combinator ($<) with signature
($<) :: (c -> a b d) -> a b c -> a b d
With that combinator you can write
f' $< g
The combinator does the following: The input of the whole arrow is fed into g, g computes some result and this result together with the input is used for evaluating f'. The ($<) is something similar to ($).
Fair enough.
... In conclusion, while I greatly appreciate you taking the time to explain your reasoning, it still looks to me like there is nothing you have gained by using arrows except adding extra unnecessary complexity in your library. So, your advice is to throw away the whole arrow stuff in hxt-10 and redefined (or rename) the combinators on the basis of monads?
Cheers,
Uwe
Yes, of course it is!!! Of course, given how much work you put into hxt, you would most likely be a fool if you actually took my advice... ;-) So, no, despite the way I am probably coming across I am not actually trying to convince you to rewrite your library from scratch to use monads. My actual goals are twofold: First (driven by genuine curiosity) to see if there is something that I missed that made arrows be the natural choice for you to use in your library. Second, to try and convince people in the future who are considering basing their libraries on arrows that they should only do this if monads do not give them enough power as library authors. Cheers, Greg