Converting Monad notation to Arrow notation

后端 未结 2 788
灰色年华
灰色年华 2021-02-04 14:01

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         


        
2条回答
  •  醉话见心
    2021-02-04 14:45

    Changing your Monad thinking to Arrow thinking

    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.

    Using () 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 input and output, but no function

    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.

    Converting Monads to Arrows

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

    Your example, at last

    (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
    

    Like 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
    

    All that ()

    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
    

提交回复
热议问题