I\'m trying to understand arrow notation, in particularly how it works with Monads. With Monads I can define the following:
f = (*2)
g = Just 5 >>= (return
The first step to translating into Arrow is to move from thinking about m b on its own to thinking about a -> m b.
With a monad, you'd write
use x = do
.....
....
doThis = do
....
...
thing = doThis >>= use
whereas an arrow always has an input, so you'd have to do
doThis' _ = do
.....
....
and then use (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c from Control.Monad do have
thing' = doThis' >=> use
>=> removes the asymmetry of >>=, and is what we would call the Kleisli arrow of the Monad.
() for input or "What if my first thing really isn't a function though?"That's OK, it's just the co-problem to if your monad doesn't produce anything (like putStrLn doesn't), whereupon you just get it to return ().
If your thing doesn't need any data, just make it a function that takes () as an argument.
doThis () = do .... ....
that way everthing has the signature a -> m b and you can chain them with >=>.
Arrows have the signature
Arrow a => a b c
which is perhaps less clear than the infix
Arrow (~>) => b ~> c
but you should still be thinking of it as analagous to b -> m c.
The main difference is that with b -> m c you have your b as an argument to a function and can do what you like with it, like if b == "war" then launchMissiles else return () but with an arrow you can't (unless it's an ArrowApply - see this question for why ArrowApply gives you Monad capabilities) - in general, an arrow just does what it does and doesn't get to switch operation based on the data, a bit like an Applicative does.
The problem with b -> m c is that there you can't partially apply it in an instance declaration to get the -> m bit from the middle, so given that b -> m c is called a Kleisli arrow, Control.Monad defines (>>>) so that after all the wrapping and unwrapping, you get f >>> g = \x -> f x >>= g - but this is equivalent to (>>>) = (>=>). (In fact, (.) is defined for Categories, rather than the forwards composition >>>, but I did say equivalent!)
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Monad m => Category (Kleisli m) where
id = Kleisli return
(Kleisli f) . (Kleisli g) = Kleisli (\b -> g b >>= f) -- composition of Kleisli arrows
instance Monad m => Arrow (Kleisli m) where
arr f = Kleisli (return . f)
first (Kleisli f) = Kleisli (\ ~(b,d) -> f b >>= \c -> return (c,d))
second (Kleisli f) = Kleisli (\ ~(d,b) -> f b >>= \c -> return (d,c))
(Try to ignore all the Kleisli and runKleisli - they're just wrapping and unwrapping monadic values - when you define your own arrow, they're not necessary.)
If we unwrap what that means for the Maybe, we get the equivalent of composing
f :: a -> Maybe b
g :: b -> Maybe c
f >>> g :: a -> Maybe c
f >>> g = \a -> case f a of -- not compilable code!
Nothing -> Nothing
Just b -> g b
and the Arrow way of applying a (pure) function is with arr :: Arrow (~>) => (b -> c) -> b ~> c
I'll fix (~->) to mean Kleisli Maybe so you can see it in action:
{-# LANGUAGE TypeOperators #-}
import Control.Arrow
type (~->) = Kleisli Maybe
g :: Integer ~-> Integer
g = Kleisli Just >>> arr (*2)
giving
ghci> runKleisli g 10
Just 20
do notation, but with input as well as output. (GHC)GHC implements the equivalent of do notation, proc notation, which lets you do
output <- arrow -< input
You're used to output <- monad but now there's the arrow -< input notation. Just as with Monads, you don't do <- on the last line, you don't do that in proc notation either.
Let's use the Maybe versions of tail and read from safe to illustrate the notation (and advertise safe).
{-# LANGUAGE Arrows #-}
import Control.Arrow
import Safe
this = proc inputList -> do
digits <- Kleisli tailMay -< inputList
number <- Kleisli readMay -<< digits
arr (*10) -<< number
Notice I've used the -<< variant of -<, which lets you use output as input by bringing things on the left of <- into scope at the right of -<.
Clearly this is equivalent to Kleisli tailMay >>> Kleisli readMay >>> arr (*10), but it's just (!) to give you the idea.
ghci> runKleisli this "H1234" -- works
Just 1234
ghci> runKleisli this "HH1234" -- readMay fails
Nothing
ghci> runKleisli this "" -- tailMay fails
Nothing
ghci> runKleisli this "10" -- works
Just 0
()Like I said, we use () if we don't have input, and as we do in Monad, return it if we don't need to output anything.
You'll see () in proc notation examples too:
thing = proc x -> do
this <- thing1 -< ()
() <- thing2 -< x
returnA -< this