Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding Arrows in Haskell

Tags:

haskell

arrows

I've been trying to get a grip on arrows since they're the basis of most FRP implementations. I think I understand the basic idea - they're related to monads but store static information at each bind operator so you can walk through a chain of arrows and look at the static information without having to evaluate the whole arrow.

But I get lost at the point where we start discussing first, second, and swap. What do 2-tuples have to do with arrows? Tutorials present the tuple stuff as if it were an obvious next step, but I'm not really seeing the connection.

For that matter, what does the arrow syntax mean intuitively?

like image 507
Bill Avatar asked Jul 01 '10 02:07

Bill


People also ask

What do the arrows mean in Haskell?

I think it is called the arrow. According to "Real World Haskell" : -> has only one meaning: it denotes a function that takes an argument of the type on the left and returns a value of the type on the right. Follow this answer to receive notifications.

What are monads Haskell?

What is a Monad? A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.

Do expressions Haskell?

As a syntactical convenience, do notation does not add anything essential, but it is often preferable for clarity and style. However, do is not needed for a single action, at all. The Haskell "Hello world" is simply: main = putStrLn "Hello world!"

Who invented monads?

The mathematician Roger Godement was the first to formulate the concept of a monad (dubbing it a "standard construction") in the late 1950s, though the term "monad" that came to dominate was popularized by category-theorist Saunders Mac Lane.


1 Answers

Please take a look in http://www.cs.yale.edu/homes/hudak/CS429F04/AFPLectureNotes.pdf, which explains how Arrows work in FRP.

2-tuples are used in defining Arrows because it's needed to represent an arrowized function taking 2 arguments.

In FRP, constants and variables are often represented as arrows which ignores its "input", e.g.

twelve, eleven :: Arrow f => f p Int
twelve = arr (const 12)
eleven = arr (const 11)

Function applications are then turned into compositions (>>>):

# (6-) 12

arr (6-) <<< twelve

Now how do we turn a 2-argument function into an arrow? For instance

(+) :: Num a => a -> a -> a

due to currying we may treat this as a function returning a function. So

arr (+) :: (Arrow f, Num a) => f a (a -> a)

now let's apply it to a constant

arr (+)             -- # f     a (a -> a)
  <<< twelve        -- # f b Int
                      :: f b     (Int -> Int)

+----------+      +-----+      +--------------+
| const 12 |----> | (+) |  ==  | const (+ 12) |
+----------+      +-----+      +--------------+

hey wait, it doesn't work. The result is still an arrow that returns a function, but we expect something akin to f Int Int. We notice that currying fails in Arrow because only composition is allowed. Therefore we must uncurry the function first

uncurry :: (a -> b -> c) -> ((a, b) -> c)

uncurry (+) :: Num a => (a, a) -> a

Then we have the arrow

(arr.uncurry) (+) :: (Num a, Arrow f) => f (a, a) a

The 2-tuple arises because of this. Then the bunch functions like &&& are needed to deal with these 2-tuples.

(&&&) :: f a b -> f a d -> f a (b, d)

then the addition can be correctly performed.

(arr.uncurry) (+)        -- # f   (a,    a) a
  <<<     twelve         -- # f b  Int
      &&& eleven         -- # f b      Int
                           :: f b           a

+--------+
|const 12|-----.
+--------+     |       +-----+      +----------+
              &&&====> | (+) |  ==  | const 23 |
+--------+     |       +-----+      +----------+
|const 11|-----'
+--------+

(Now, why don't we need things like &&&& for 3-tuples for functions having 3 arguments? Because a ((a,b),c) can be used instead.)


Edit: From John Hughes's original paper Generalising Monads to Arrows, it states the reason as

4.1 Arrows and Pairs

However, even though in case of monads the operators return and >>= are all we need to begin writing useful code, for arrows the analogous operators arr and >>> are not sufficient. Even the simple monadic addition function that we saw earlier

   add :: Monad m => m Int -> m Int -> m Int
   add x y = x >>= \u -> (y >>= \v -> return (u + v))

cannot yet be expressed in an arrow form. Making dependence on an input explicit, we see that an analogous definition should take the form

   add :: Arrow a => a b Int -> a b Int -> a b Int
   add f g = ...

where we must combine f and g in sequence. The only sequencing operator available is >>>, but f and g do not have the right types to be composed. Indeed, the add function needs to save the input of type b across the computation of f, so as to be able to supply the same input to g. Likewise the result of f must be saved across the computation of g, so that the two results can eventually be added together and returned. The arrow combinators so far introduced give us no way to save a value across another computation, and so we have no alternative but to introduce another combinator.

like image 175
kennytm Avatar answered Oct 16 '22 23:10

kennytm