I'm still getting to grips with defining and using Arrows in Haskell. While defining new arrows, it is much easier for me to think in terms of *** or &&& rather than first and second, as most of the time I want special processing for when two arrows are combined.
However the Arrow class does not allow defining the arrow in terms of arr and *** or &&&, and requires a definition of first to be included. This means that I am forced to write code like the following -
instance Arrow X where
arr f = ...
f (***) g = ...
first f = f *** arr id
It seems to me that there would have been no harm in including a default definition of 'first' as well in the Control.Arrow module. That would have allowed us to choose between defining either first or ***.
Is there a good reason why a default definition of first was not included in the Arrow class? The only reason that I can think of is that the user may leave out the definition of first and *** both and then you would have circular definitions, but is that the only reason?
(->) 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.
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.
The left arrow gets used in do notation as something similar to variable binding, in list comprehensions for the same (I'm assuming they are the same, as list comprehensions look like condensed do blocks), and in pattern guards, which have the form (p <- e). All of those constructs bind a new variable.
Data.Monoid has a similar missing alternative minimal-complete-definition: mconcat. Like with Arrow, it is missing this alternative out of performance reasons. mappend a b = mconcat[a,b]
is inefficient because of the pattern-matching in mconcat.
With Arrows, the inefficiency is less obvious but more brutal: Think about not being able to optimize arr id
away:
Using CleisliArrow as example Arrow, it is like this:
first f (x,y) = do
x' <- f x
return (x',y)
And it would be:
first f (x,y) = do
x' <- f x
y' <- arr id y
return (x',y')
The arr function cannot pattern-match the id function and has therefore to introduce additional overhead to wrap that function.
I actually believe it's the circularity that stopped someone from writing the default methods. But as @camccann pointed out, this should stop anyone. Suggest a change!
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