Arrow tutorial
From HaskellWiki
m (Changed the ASCII diagram for the part after hOutput) |
|||
| (15 intermediate revisions not shown.) | |||
| Line 1: | Line 1: | ||
| - | + | [[Category:Tutorials]] | |
| + | [[Category:Arrow]] | ||
| + | <haskell> | ||
| + | |||
| + | > {-# LANGUAGE Arrows #-} | ||
| + | > module ArrowFun where | ||
| + | > import Control.Arrow | ||
| + | > import Control.Category | ||
| + | > import Prelude hiding (id,(.)) | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | == The Arrow == | ||
| + | Arrow a b c represents a process that takes as input something of | ||
| + | type b and outputs something of type c. | ||
| + | |||
| + | Arr builds an arrow out of a function. This function is | ||
| + | arrow-specific. Its signature is | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | arr :: (Arrow a) => (b -> c) -> a b c | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | Arrow composition is achieved with (>>>). This takes two arrows | ||
| + | and chains them together, one after another. It is also arrow- | ||
| + | specific. Its signature is: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | (>>>) :: (Arrow a) => a b c -> a c d -> a b d | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | First and second make a new arrow out of an existing arrow. They | ||
| + | perform a transformation (given by their argument) on either | ||
| + | the first or the second item of a pair. These definitions are | ||
| + | arrow-specific. Their signatures are: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | first :: (Arrow a) => a b c -> a (b, d) (c, d) | ||
| + | second :: (Arrow a) => a b c -> a (d, b) (d, c) | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | First and second may seem pretty strange at first, but they'll make sense | ||
| + | in a few minutes. | ||
| + | |||
| + | That's it for the arrow-specific definitions. | ||
| + | |||
| + | == A Simple Arrow == | ||
| + | Let's define a really simple arrow as an example. Our simple arrow is | ||
| + | just a function mapping an input to an output. We don't really need | ||
| + | arrows for something this simple, but we could use something this | ||
| + | simple to explain arrows. | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | > newtype SimpleFunc a b = SimpleFunc { | ||
| + | > runF :: (a -> b) | ||
| + | > } | ||
| + | > | ||
| + | > instance Arrow SimpleFunc where | ||
| + | > arr f = SimpleFunc f | ||
| + | > first (SimpleFunc f) = SimpleFunc (mapFst f) | ||
| + | > where mapFst g (a,b) = (g a, b) | ||
| + | > second (SimpleFunc f) = SimpleFunc (mapSnd f) | ||
| + | > where mapSnd g (a,b) = (a, g b) | ||
| + | > | ||
| + | > instance Category SimpleFunc where | ||
| + | > (SimpleFunc g) . (SimpleFunc f) = SimpleFunc (g . f) | ||
| + | > id = arr id | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | == Some Arrow Operations == | ||
| + | Now lets define some operations that are generic to all arrows. | ||
| + | |||
| + | Split is an arrow that splits a single value into a pair of duplicate | ||
| + | values: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | > split :: (Arrow a) => a b (b, b) | ||
| + | > split = arr (\x -> (x,x)) | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | Unsplit is an arrow that takes a pair of values and combines them | ||
| + | to return a single value: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | > unsplit :: (Arrow a) => (b -> c -> d) -> a (b, c) d | ||
| + | > unsplit = arr . uncurry | ||
| + | > -- arr (\op (x,y) -> x `op` y) | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | (***) combines two arrows into a new arrow by running the two arrows | ||
| + | on a pair of values (one arrow on the first item of the pair and one arrow on the | ||
| + | second item of the pair). | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | f *** g = first f >>> second g | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | (&&&) combines two arrows into a new arrow by running the two arrows on | ||
| + | the same value: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | f &&& g = split >>> first f >>> second g | ||
| + | -- = split >>> f *** g | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | LiftA2 makes a new arrow that combines the output from two arrows using | ||
| + | a binary operation. It works by splitting a value and operating on | ||
| + | both halfs and then combining the result: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | > liftA2 :: (Arrow a) => (b -> c -> d) -> a e b -> a e c -> a e d | ||
| + | > liftA2 op f g = split >>> first f >>> second g >>> unsplit op | ||
| + | > -- = f &&& g >>> unsplit op | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | |||
| + | == An Example == | ||
| + | Now let's build something using our simple arrow definition and | ||
| + | some of the tools we just created. We start with two simple | ||
| + | arrows, f and g. F halves its input and g triples its input and | ||
| + | adds one: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | > f, g :: SimpleFunc Int Int | ||
| + | > f = arr (`div` 2) | ||
| + | > g = arr (\x -> x*3 + 1) | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | We can combine these together using liftA2: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | > h :: SimpleFunc Int Int | ||
| + | > h = liftA2 (+) f g | ||
| + | > | ||
| + | > hOutput :: Int | ||
| + | > hOutput = runF h 8 | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | What is h? How does it work? | ||
| + | The process defined by h is (split >>> first f >>> second g >>> unsplit (+)). | ||
| + | Lets work through an application of h to some value, 8: | ||
| + | |||
| + | 8 -> (8, 8) split | ||
| + | (8, 8) -> (4, 8) first f (x `div` 2 of the first element) | ||
| + | (4, 8) -> (4, 25) second g (3*x + 1 of the second element) | ||
| + | (4, 25) -> 29 applies (+) to tuple elements. | ||
| + | |||
| + | +------> f ---------+ | ||
| + | | v | ||
| + | 8 ---> (split) (unsplit (+)) ----> 29 | ||
| + | | ^ | ||
| + | +------> g ---------+ | ||
| + | |||
| + | so we see that h is a new arrow that when applied to 8, applies 8 to f | ||
| + | and applies 8 to g and adds the results. | ||
| + | |||
| + | A lot of juggling occurred to get the plumbing right since | ||
| + | h wasn't defined as a linear combination of arrows. GHC has | ||
| + | a do-notation that simplifies this in a similar way to how | ||
| + | do-notation simplifies monadic computation. To use this | ||
| + | notation you must specify the -farrows flag. The h function | ||
| + | can be defined as: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | > h' :: SimpleFunc Int Int | ||
| + | > h' = proc x -> do | ||
| + | > fx <- f -< x | ||
| + | > gx <- g -< x | ||
| + | > returnA -< (fx + gx) | ||
| + | > | ||
| + | > hOutput' :: Int | ||
| + | > hOutput' = runF h' 8 | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | |||
| + | == Kleisli Arrows == | ||
| + | Let's move on to something a little fancier now: Kleisli arrows. | ||
| + | A Kleisli arrow (Kleisli m a b) is the arrow (a -> m b) for all | ||
| + | monads. It's defined in Control.Arrows similarly to our SimpleFunc: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | newtype Kleisli m a b = Kleisli { | ||
| + | runKleisli :: (a -> m b) | ||
| + | } | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | It comes complete with its own definitions for arr, first, second and | ||
| + | (>>>). This means that all multi-value functions (a -> [b]) are already | ||
| + | defined as Kleisli arrows (because [] is a monad)! (>>>) performs | ||
| + | composition, keeping track of all the multiple results. Split, (&&&) | ||
| + | and (***) are all defined as before. So for example: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | > plusminus, double, h2 :: Kleisli [] Int Int | ||
| + | > plusminus = Kleisli (\x -> [x, -x]) | ||
| + | > double = arr (* 2) | ||
| + | > h2 = liftA2 (+) plusminus double | ||
| + | > | ||
| + | > h2Output :: [Int] | ||
| + | > h2Output = runKleisli h2 8 | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | == A Teaser == | ||
| + | Finally, here is a little teaser. There is an arrow function called | ||
| + | returnA which returns an identity arrow. There is an ArrowPlus class | ||
| + | that includes a zeroArrow (which for the list monad is an arrow that | ||
| + | always returns the empty list) and a <+> operator (which takes the | ||
| + | results from two arrows and concatenates them). We can build up | ||
| + | some pretty interesting string transformations (the multi-valued | ||
| + | function String -> [String]) using Kleisli arrows: | ||
| + | |||
| + | <haskell> | ||
| + | |||
| + | > main :: IO () | ||
| + | > main = do | ||
| + | > let | ||
| + | > prepend x = arr (x ++) | ||
| + | > append x = arr (++ x) | ||
| + | > withId t = returnA <+> t | ||
| + | > xform = (withId $ prepend "<") >>> | ||
| + | > (withId $ append ">") >>> | ||
| + | > (withId $ ((prepend "!") >>> (append "!"))) | ||
| + | > xs = ["test", "foobar"] >>= (runKleisli xform) | ||
| + | > mapM_ putStrLn xs | ||
| + | |||
| + | </haskell> | ||
| + | |||
| + | An important observation here is that | ||
| + | f >>> g | ||
| + | |||
| + | is multi-valued composition (g . f), and | ||
| + | (withId f) >>> (withId g) = | ||
| + | (returnA <+> f) >>> (returnA <+> g) = | ||
| + | ((arr id) <+> f) >>> ((arr id) <+> g) | ||
| + | |||
| + | which, when applied to an input x, returns all values: | ||
| + | ((id . id) x) ++ ((id . f) x) ++ ((id . g) x) ++ ((g . f) x) = | ||
| + | x ++ (f x) ++ (g x) ++ ((g . f) x) | ||
| + | |||
| + | which are all permutations of using arrows f and g. | ||
| + | |||
| + | == Tutorial Meta == | ||
| + | The wiki file source is literate Haskell. Save the source in a file called ArrowFun.lhs to compile it (or run in GHCi). | ||
| + | |||
| + | The code is adapted to GHC 6.10.1; use [http://www.haskell.org/haskellwiki/?title=Arrow_tutorial&oldid=15443] for older versions of GHC and other Haskell implementations. | ||
| + | |||
| + | * Original version - Nov 19, 2006, Tim Newsham. | ||
| + | \ | ||
Current revision
> {-# LANGUAGE Arrows #-} > module ArrowFun where > import Control.Arrow > import Control.Category > import Prelude hiding (id,(.))
Contents |
1 The Arrow
Arrow a b c represents a process that takes as input something of type b and outputs something of type c.
Arr builds an arrow out of a function. This function is arrow-specific. Its signature is
arr :: (Arrow a) => (b -> c) -> a b c
Arrow composition is achieved with (>>>). This takes two arrows and chains them together, one after another. It is also arrow- specific. Its signature is:
(>>>) :: (Arrow a) => a b c -> a c d -> a b d
First and second make a new arrow out of an existing arrow. They perform a transformation (given by their argument) on either the first or the second item of a pair. These definitions are arrow-specific. Their signatures are:
first :: (Arrow a) => a b c -> a (b, d) (c, d) second :: (Arrow a) => a b c -> a (d, b) (d, c)
First and second may seem pretty strange at first, but they'll make sense in a few minutes.
That's it for the arrow-specific definitions.
2 A Simple Arrow
Let's define a really simple arrow as an example. Our simple arrow is just a function mapping an input to an output. We don't really need arrows for something this simple, but we could use something this simple to explain arrows.
> newtype SimpleFunc a b = SimpleFunc { > runF :: (a -> b) > } > > instance Arrow SimpleFunc where > arr f = SimpleFunc f > first (SimpleFunc f) = SimpleFunc (mapFst f) > where mapFst g (a,b) = (g a, b) > second (SimpleFunc f) = SimpleFunc (mapSnd f) > where mapSnd g (a,b) = (a, g b) > > instance Category SimpleFunc where > (SimpleFunc g) . (SimpleFunc f) = SimpleFunc (g . f) > id = arr id
3 Some Arrow Operations
Now lets define some operations that are generic to all arrows.
Split is an arrow that splits a single value into a pair of duplicate values:
> split :: (Arrow a) => a b (b, b) > split = arr (\x -> (x,x))
Unsplit is an arrow that takes a pair of values and combines them to return a single value:
> unsplit :: (Arrow a) => (b -> c -> d) -> a (b, c) d > unsplit = arr . uncurry > -- arr (\op (x,y) -> x `op` y)
(***) combines two arrows into a new arrow by running the two arrows on a pair of values (one arrow on the first item of the pair and one arrow on the second item of the pair).
f *** g = first f >>> second g
(&&&) combines two arrows into a new arrow by running the two arrows on the same value:
f &&& g = split >>> first f >>> second g -- = split >>> f *** g
LiftA2 makes a new arrow that combines the output from two arrows using a binary operation. It works by splitting a value and operating on both halfs and then combining the result:
> liftA2 :: (Arrow a) => (b -> c -> d) -> a e b -> a e c -> a e d > liftA2 op f g = split >>> first f >>> second g >>> unsplit op > -- = f &&& g >>> unsplit op
4 An Example
Now let's build something using our simple arrow definition and some of the tools we just created. We start with two simple arrows, f and g. F halves its input and g triples its input and adds one:
> f, g :: SimpleFunc Int Int > f = arr (`div` 2) > g = arr (\x -> x*3 + 1)
We can combine these together using liftA2:
> h :: SimpleFunc Int Int > h = liftA2 (+) f g > > hOutput :: Int > hOutput = runF h 8
What is h? How does it work? The process defined by h is (split >>> first f >>> second g >>> unsplit (+)). Lets work through an application of h to some value, 8:
8 -> (8, 8) split (8, 8) -> (4, 8) first f (x `div` 2 of the first element) (4, 8) -> (4, 25) second g (3*x + 1 of the second element) (4, 25) -> 29 applies (+) to tuple elements.
+------> f ---------+
| v
8 ---> (split) (unsplit (+)) ----> 29
| ^
+------> g ---------+
so we see that h is a new arrow that when applied to 8, applies 8 to f and applies 8 to g and adds the results.
A lot of juggling occurred to get the plumbing right since h wasn't defined as a linear combination of arrows. GHC has a do-notation that simplifies this in a similar way to how do-notation simplifies monadic computation. To use this notation you must specify the -farrows flag. The h function can be defined as:
> h' :: SimpleFunc Int Int > h' = proc x -> do > fx <- f -< x > gx <- g -< x > returnA -< (fx + gx) > > hOutput' :: Int > hOutput' = runF h' 8
5 Kleisli Arrows
Let's move on to something a little fancier now: Kleisli arrows. A Kleisli arrow (Kleisli m a b) is the arrow (a -> m b) for all monads. It's defined in Control.Arrows similarly to our SimpleFunc:
newtype Kleisli m a b = Kleisli { runKleisli :: (a -> m b) }
It comes complete with its own definitions for arr, first, second and (>>>). This means that all multi-value functions (a -> [b]) are already defined as Kleisli arrows (because [] is a monad)! (>>>) performs composition, keeping track of all the multiple results. Split, (&&&) and (***) are all defined as before. So for example:
> plusminus, double, h2 :: Kleisli [] Int Int > plusminus = Kleisli (\x -> [x, -x]) > double = arr (* 2) > h2 = liftA2 (+) plusminus double > > h2Output :: [Int] > h2Output = runKleisli h2 8
6 A Teaser
Finally, here is a little teaser. There is an arrow function called returnA which returns an identity arrow. There is an ArrowPlus class that includes a zeroArrow (which for the list monad is an arrow that always returns the empty list) and a <+> operator (which takes the results from two arrows and concatenates them). We can build up some pretty interesting string transformations (the multi-valued function String -> [String]) using Kleisli arrows:
> main :: IO () > main = do > let > prepend x = arr (x ++) > append x = arr (++ x) > withId t = returnA <+> t > xform = (withId $ prepend "<") >>> > (withId $ append ">") >>> > (withId $ ((prepend "!") >>> (append "!"))) > xs = ["test", "foobar"] >>= (runKleisli xform) > mapM_ putStrLn xs
An important observation here is that
f >>> g
is multi-valued composition (g . f), and
(withId f) >>> (withId g) = (returnA <+> f) >>> (returnA <+> g) = ((arr id) <+> f) >>> ((arr id) <+> g)
which, when applied to an input x, returns all values:
((id . id) x) ++ ((id . f) x) ++ ((id . g) x) ++ ((g . f) x) = x ++ (f x) ++ (g x) ++ ((g . f) x)
which are all permutations of using arrows f and g.
7 Tutorial Meta
The wiki file source is literate Haskell. Save the source in a file called ArrowFun.lhs to compile it (or run in GHCi).
The code is adapted to GHC 6.10.1; use [1] for older versions of GHC and other Haskell implementations.
- Original version - Nov 19, 2006, Tim Newsham.
\
