Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I am learning functionnal programming with Haskell, and I try to grab concepts by first understanding why do I need them.

I would like to know the goal of arrows in functional programming languages. What problem do they solve? I checked http://en.wikibooks.org/wiki/Haskell/Understanding_arrows and http://www.cse.chalmers.se/~rjmh/afp-arrows.pdf. All I understand is that they are used to describe graphs for computations, and that they allow easier point free style coding.

The article assume that point free style is generally easier to understand and to write. This seems quite subjective to me. In another article (http://en.wikibooks.org/wiki/Haskell/StephensArrowTutorial#Hangman:_Main_program), a hangman game is implemented, but I cannot see how arrows makes this implementation natural.

I could find a lot of papers describing the concept, but nothing about the motivation.

What I am missing?

share|improve this question
add comment

5 Answers

up vote 21 down vote accepted

I realize I'm coming late to the party, but you've had two theoretical answers here, and I wanted to provide a practical alternative to chew over. I'm coming at this as a relative Haskell noob who nonetheless has been recently force-marched through the subject of Arrows for a project I'm currently working on.

First, you can productively solve most problems in Haskell without reaching for Arrows. Some notable Haskellers genuinely do not like and do not use them (see here, here, and here for more on this). So if you're saying to yourself "Hey, I don't need these," understand that you may genuinely be correct.

What I found most frustrating about Arrows when I first learned them was how the tutorials on the subject inevitably reached for the analogy of circuitry. If you look at Arrow code -- the sugared variety, at least -- it resembles nothing so much as a Hardware Defnition Language. Your inputs line up on the right, your outputs on the left, and if you fail to wire them all up properly they simply fail to fire. I thought to myself: Really? Is this where we've ended up? Have we created a language so completely high-level that it once again consists of copper wires and solder?

The correct answer to this, as far as I've been able to determine, is: Actually, yes. The killer use case right now for Arrows is FRP (think Yampa, games, music, and reactive systems in general). The problem facing FRP is largely the same problem facing all other synchronous messaging systems: how to wire a continuous stream of inputs into a continuous stream of outputs without dropping relevant information or springing leaks. You can model the streams as lists -- several recent FRP systems use this approach -- but when you have a lot of inputs lists become almost impossible to manage. You need to insulate yourself from the current.

What Arrows allow in FRP systems is the composition of functions into a network while at the same time entirely abstracting away any reference at all to the underlying values being passed by those functions. If you're new to FP, this can be confusing at first, and then mind-blowing when you've absorbed the implications of it. You've only recently absorbed the idea that functions can be abstracted, and how to understand a list like [(*), (+), (-)] as being of type [(a -> a -> a)]. With Arrows, you can push the abstraction one layer further.

This additional ability to abstract carries with it its own dangers. For one thing, it can push GHC into corner cases where it doesn't know what to make of your type assumptions. You'll have to be prepared to think at the type level -- this is an excellent opportunity to learn about kinds and RankNTypes and other such topics.

There are also a number of examples of what I'd call "Stupid Arrow Stunts" where the coder reaches for some Arrow combinator just because he or she wants to show off a neat trick with tuples. (Here's my own trivial contribution to the madness.) Feel free to ignore such hot-dogging when you come across it in the wild.

NOTE: As I mentioned above, I'm a relative noob. If I've promulgated any misconceptions above, please feel free to correct me.

share|improve this answer
    
I am happy that I had not accepted anything yet. Thank you for providing this answer. It is more focused on users. The exemples are great. The subjective parts are clearly defined and balanced. I hope that people who have upvoted this question will come back and see this. –  Simon Nov 10 '11 at 11:26
add comment

This is kind of a "soft" answer, and I'm not sure if any reference actually states it in this manner, but this is how I've come to think of arrows:

An arrow type A b c is basically a function b -> c but with more structure in the same way that a monadic value M a has more structure than a plain old a.

Now what that extra structure happens to be depends on the particular arrow instance you're talking about. Just as with monads IO a and Maybe a each have different additional structure.

The thing that you get with monads is an inability to go from an M a to an a. Now this may seem like a limitation, but it's actually a feature: the type system is protecting you from turning a monadic value into a plain old value. You can only make use of the value by participating in the monad via >>= or the primitive operations of the particular monad instance.

Likewise the thing that you get from A b c is an inability to construct a new b-consuming c-producing "function". The arrow is protecting you from consuming the b and creating a c except by participating in the various arrow combinators or by using the primitive operations of the particular arrow instance.

For example the signal functions in Yampa are roughly (Time -> a) -> (Time -> b), but additionally they have to obey a certain causality restriction: the output at time t is determined by the past values of the input signal: you can't look into the future. So what they do is instead of programming with (Time -> a) -> (Time -> b), you program with SF a b and you build your signal functions out of primitives. It so happens that since SF a b behaves a lot like a function, so that common structure is what's called an "arrow".

share|improve this answer
    
+1 for the yampa example. –  Simon Oct 18 '11 at 12:01
add comment

I like to think of Arrows, like Monads and Functors, as allowing the programmer to do exotic compositions of functions.

Without Monads or Arrows (and Functors), composition of functions in a functional language is limited to applying one function to the result of another function. With monads and functors, you can define two functions, and then write separate reusable code which specify how those functions, in the context of the particular monad, interact with each other and with the data which is passed into them. This code is placed within the bind code of the Monad. So a monad is one one view, just a container for reusuable bind code. Functions compose differently within the context of one monad from another monad.

A simple example is the Maybe monad, where there is code in the bind function such that if a function A is composed with a function B within a Maybe monad, and B produces a Nothing, then the bind code will ensure that the composition of the two functions outputs a Nothing, without bothering to apply A to the Nothing value coming out from B. If there were no monad, the programmer would have to write code into A to test for a Nothing input.

Monads also mean that the programmer does not need to explicitly type the parameters which each function requires into the source code - the bind function handles parameter passing. So using monads, the source code can begin to look more like a static chain of function names, rather than looking as though function A "calls" function B with parameters C and D - the code starts to feel more like an electronic circuit than a moving machine - more functional than imperative.

Arrows also connect functions together with a bind function, providing reusable functionality and hiding parameters. But Arrows can themselves be connected together and composed, and can optionally route data to other Arrows at runtime. Now you can apply data to two paths of Arrows, which "do different things" to the data, and reassemble the result. Or you can select which branch of Arrows to pass the data to, depending on some value in the data. The resulting code is even more like an electronic circuit, with switches, delays, integration etc. The program looks very static, and you should not be able to see much manipulation of data going on. There are fewer and fewer parameters to think about, and less need to think about what values parameters may or may not take.

Writing an Arrowized program mostly involves selecting off the shelf Arrows such as splitters, switches, delays and integrators, lifting functions into those Arrows, and connecting the Arrows together to form bigger Arrows. In Arrowized Functional Reactive Programming, the Arrows form a loop, with input from the world being combined with output from the last iteration of the program, so that the output reacts to real world input.

One of the real world values is time. In Yampa, the Signal Function Arrow invisibly threads the time parameter through the computer program - you never access the time value, but if you connect an integrator arrow into the program, it will output values integrated over time which you can then use to pass to other arrows.

share|improve this answer
    
but this sounds like an applicative functor (some wrapper around a function, that provides helper functions, in some specific context, for reusing already existing functions for the wrapped-types). i definitely need to read more to understand, but maybe you can help, by pointing out what i am missing –  Belun May 14 '13 at 9:35
add comment

Just an addition to the other answers: Personally it helps me a lot to understand what such a concept is (mathematically) and how it relates to other concepts I know.

In the case of arrows, I found the following paper helpful - it compares monads, applicative functors (idioms) and arrows: Idioms are oblivious, arrows are meticulous, monads are promiscuous by Sam Lindley, Philip Wadler and Jeremy Yallop.

Also I believe nobody mentioned this link which may provide you some ideas and literature on the subject.

share|improve this answer
add comment

The reason for arrows is as follows:

  1. In category theory there is a concept called arrows. This is slightly similar to functions, but a lot more flexible.
  2. Haskell supports functions by default, but needs separate implementation for the arrows.
  3. Arrows in haskell tries to implement the category theory concept of arrows.
  4. The syntax for arrows is slightly more complicated than normal use of functions in haskell. This is both because of flexibility and because it prevents newbies to shoot themselves to the foot with category theory.
  5. Arrows and functors are the next level after you've learned functional programming using normal functions.
  6. Arrows are "easier" to understand once you understand the category theory concept. This however is not easy to archieve.
share|improve this answer
2  
sorry but phrases like "In category theory there is a concept called arrows. This is slightly similar to functions, but a lot more flexible." makes things even more complicated, honestly. This is not answer at all. –  shabunc Oct 17 '11 at 16:04
    
If you don't have big problems with the flexibility of existing haskell functions, then functions are probably better solution than arrows. Arrows are more complicated, and for a good reason. –  tp1 Oct 17 '11 at 16:27
4  
I'm not the author of thee question, but as far as I understand the question is "could one provide practical examples of using arrows". And you actually answer "arrows are more powerful versions of functions with slightly more complicated syntax". Honestly, not very inspiring answer. –  shabunc Oct 17 '11 at 16:40
    
well, both the syntax and complexity of arrows is actually necessary. It's the same thing as a tree is more complicated than an array. Or graph is more complex than tree. Monad is more complicated than graph. And Arrows are more complicated than monads. It's just one step in the path of learning fundamental data structures used in programming. –  tp1 Oct 17 '11 at 16:49
    
@tp1: Looking at your posts on Haskell, I can tell that you have no deep understanding of anything but try to appear "knowledgable" by hand-waving statements. Please refrain from that. programmers.stackexchange.com/search?q=user:35639+[haskell] –  Phil Jun 7 '13 at 8:08
show 1 more comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.