Who is afraid of arrows,
was Re: [Haskell-cafe] ANNOUNCE: Haskell XML Toolbox Version 9.0.0
Sebastiaan Visser
haskell at fvisser.nl
Tue Oct 12 09:29:21 EDT 2010
Gregory,
I use arrows (especially list arrows) in a lot of my projects and find them (after some training) easier to work with than monands. Code that I write point-free using arrows generally contains fewer bugs than code I write in monadic style.
On Oct 11, 2010, at 8:48 PM, Gregory Crosswhite wrote:
> Uwe,
>
> Thank you for your reply.
>
> On 10/11/10 6:20 AM, Uwe Schmidt wrote:
>> I thing, this is not a question of functionality, it's a question of style.
>> Of course everything in hxt could have been done with monads,
>> but does this also mean: Everything must have been done with monads?
>>
> No, but there is no point in using a formalism that adds complexity without adding functionality. Arrows are more awkward to use than monads because they were intentionally designed to be less powerful than monads in order to cover situations in which one could not use a monad. When your problem is solved by a monad there is no point in using arrows since an arrow require you to jump through extra hoops to accomplish the same goal.
I think it is unfair to say that arrows add complexity over monads. This statement can only be true to people who actually know monads and do not know arrows. This has more to do with the direction of one's knowledge than the 'complexity' (whatever this means) of a programming abstraction.
Don't see arrows a less restricted monads, see them as a generalization of functions, than it will all make sense. Use them in situations that need generalization of functions, not in cases that require the power of monads.
>> Arrows are a generalisation of functions, so if you know all about working
>> with functions you know (almost) all about working with arrows.
>> When generalizing a pure algorithm, something like "f3 . f2 . f1",
>> or in Unix pipe style "f1>>> f2>>> f3", you don't have to rewrite
>> the code, you just generalize function composition.
>
> 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.
I'd rather use (.) for composition of expressions than (<=<) and I'd rather use the 'id' for identity than return. Writing my arrow computations point-free as if they were functions feels far less clumsy and a far more readable than monadic style.
>> When constructing code, it is of course sometimes simpler to start with a
>> point wise version and then refactor and rewrite it into a more compact point
>> free version. The problem with arrows seems, that the arrow style forces to
>> start with the point free style. And that may be the main hurdle in mind.
>>
> 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. Second, they place restrictions on how you define the input arguments of the arrow because you can't feed the output of one arrow into to input of the next unless said input is captured in the arrows type.
This restriction, though not always, can be very useful. This restriction allows you to do full inspection of the arrow expression. This inspection can in some cases be used to serialize an arrow computation of statically optimize it to a more efficient form.
When you don't need the power of Monads (or ArrowApply) why use a formalism that does provide this power? That will only make it harder to reason about your program, which isn't quite the Haskell way.
> To be more concrete about my second point, suppose you have some monadic action
>
> f :: a -> b -> m c
>
> How would you structure this same action as an arrow? One thing that you could do is take one of the arguments and turn it into the input of the arrow:
>
> f' :: a -> Arrow b c
>
> But now you can't feed the output of an arrow into the first argument. Alternatively, you could pack both arguments into the input of the arrow:
>
> f'' :: Arrow (a,b) c
>
> Great, but now you have made it more awkward to use f'' because you have to always pack the arguments into a tuple, so that for example if one of the arguments is a constant then you can no longer easily use currying. The advantage of f over both alternatives is that you don't have to waste any of your time fitting multiple input arguments into the input type of an arrow.
>
> In fact, if the first argument to f'' is usually a constant, then f'' is arguably more awkward to use in a point-free setting than f, because instead of being able to write
>
> a >=> f 42 >=> b
>
> You have to write something like
>
> a >>> (const 42 &&& id) >>> f'' >>> b
>
> Of course, if the first argument were *always* a constant then you could use f', but then you lose the ability to ever feed in the output of an arrow into the first argument. So in short, arrows force you to make choices and trade-offs that never come up when using monads.
This problem is obviously an engineering problem and not a conceptual problem. Using the tuple input gives you more power but sometimes less convenience, factoring out the argument to true function space might be more pleasant to use but is sometimes to restrictive.
The choice depends on your problem domain. In most situations I've encountered this assessment does not surface a lot.
> 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.
I've worked with HXT quite a lot and find XML arrows very convenient.
> Cheers,
> Greg
Gr,
Sebastiaan
More information about the Haskell-Cafe
mailing list