11
votes

Arrows are often described as generalization of functions (statically generated functions only, i.e. there's no support for partial application / closures). However, at least looking at Arrows as they are modeled in Haskell, I can't see how they can generalize functions of multiple arguments that return a single result (a result that in general might not be a tuple). I'm trying to envision how using just the arrows interface one could arrive at a composition of arrows that produces a single result that in general might not be a tuple. Is there a way to do this or is this a deliberate limitation on the power of the Arrow type?

From what I understand arrows provide the ability to compose static (possibly parallel) pipelines but they can't 'fold' the tuples of outputs into a single final result. Am wrong or I missing something?

2
Have you looked at ArrowApply?Daniel Wagner
ArrowApply does solve the problem of multiple arguments, however it brings the additional power of partial application / closures into the mix as well...Dr DR
To be honest, I'm not sure I understand the question. Since functions are in fact arrows (there is an instance Arrow (->)) it is clear that arrows generalize functions. So what does "true generalization" mean? What would have to be the case for arrows to be a "true" generalization of functions?Daniel Wagner
I admit the question might not make sense and might reflect a misunderstanding on my part. I couldn't see how composition of arrows of multiple arguments was supported by the Arrow interface, but as the answers below point out, it's not a problem, just construct an arrow Arrow (a,b) c and compose away. I thought this was cheating in a sense because it depends on lifting a certain kind of function (one that takes a tuple) into the Arrow but I guess that's not really a problem.Dr DR
@Daniel Wagner I meant "true generalization" in an informal sense of "Does the generalization capture everything we want it to?". I know Haskell defines the function type as an instance of the Arrow type class. For whatever reason however, it didn't occur to me that explicitly defining an Arrow whose input type is a tuple is perfectly reasonable, just as declaring functions with a tuple as an argument (i.e. non-curried multi-argument functions) is valid. Do you think I should reword the question?Dr DR

2 Answers

9
votes

I can't see how they can generalize functions of multiple arguments that return a single result

Just let the input type be a tuple, and the output a plain value. For example, take the arrow

plus :: a (num, num) num
let plus = arr (\(a, b) -> a + b) -- arr (uncurry (+))

Alternatively, you can take "nested arrows" - a curried function with multiple arguments is nothing but a function that returns a function. So we'd have an arrow whose result is another arrow:

plus :: a num (a num num)
let plus = arr (arr . (+))

and to use that, we need an ArrowApply instance. First, you'd combine the arrow with an other arrow that creates the second argument from your input

plusWithIncrement :: a num (a num num, num)
let plusWithIncrement = plus &&& arr (+1)

and then you could run that

plusWithIncrement >>> app :: a num num

(which is an overcomplicated way of writing arr (\x -> x + (x+1)))

6
votes

You can think of the function with type

f :: a -> b -> c

as a function that takes a value of type a and produces another function of type b -> c. In other words,

f :: a -> (b -> c)

The generalization to arrows is pretty straightforward:

f :: Arrow a (Arrow b c)

In order to do function composition with multiple variables, you have to do some crazy semantics-fu with the (.) operator, or (<<<) for arrows. The same reason that makes functions with multiple arguments pointfree cumbersome hinders the syntax from expressing arrows in this way, which is why there are so many combinators for using tuples. Also, nothing's stopping you from defining an arrow that maps a tuple to a value. The arr function turns arbitrary functions into arrows!

f :: (a, b) -> c
af :: Arrow (a, b) c
af = arr f