11

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?

Dr DR
  • 667
  • 1
  • 5
  • 8
  • Have you looked at `ArrowApply`? – Daniel Wagner Apr 14 '15 at 00:41
  • 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 Apr 14 '15 at 00:47
  • 7
    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 Apr 14 '15 at 01:03
  • 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 Apr 14 '15 at 01:07
  • @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 Apr 16 '15 at 02:44

2 Answers2

9

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)))

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    I had it stuck in my mind for some reason that constructing an arrow with an explicit tuple type as input was somehow cheating but I realize this is business as usual. As you point out, the curried approach requires ArrowApply which increases the power of the Arrow type significantly: they could now be constructed dyamically just like functions in lambda calculus. – Dr DR Apr 14 '15 at 01:22
6

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
Community
  • 1
  • 1
Mokosha
  • 2,737
  • 16
  • 33
  • 1
    The nested arrow approach requires ArrowApply but as you point out all I really need is an arrow accepting a tuple. I've accepted Bergi's answer only because it was more explicit about the nested arrow requiring an apply function to evaluate. – Dr DR Apr 14 '15 at 01:11