将Monad表示法转换为箭头表示法

时间:2014-02-16 09:29:36

标签: haskell monads arrows

我正在尝试理解箭头符号,特别是它如何与Monads一起使用。使用Monads,我可以定义以下内容:

f = (*2)
g = Just 5 >>= (return . f)

gJust 10

如何使用箭头符号进行上述操作?

2 个答案:

答案 0 :(得分:10)

将你的Monad思想改为Arrow思考

转换为箭头的第一步是从单独考虑m b到考虑a -> m b

使用monad,你会写

use x = do
   .....
   ....
doThis = do
   ....
   ...

thing = doThis >>= use

而箭头总是有输入,所以你必须这样做

doThis' _ = do
   .....
   ....

然后使用(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c中的Control.Monad确实

thing' = doThis' >=> use

>=>消除了>>=的不对称性,这就是我们称之为Monad的Kleisli箭头。

使用()作为输入或“如果我的第一件事真的不是函数怎么办?”

没关系,如果你的monad没有产生任何东西(比如putStrLn没有),那就是共同的问题,然后你就把它拿到return ()

如果你的东西不需要任何数据,只需将它作为一个以()为参数的函数。

doThis()= do       ....       ....

这样,everthing具有签名a -> m b,您可以使用>=>链接它们。

箭头有输入和输出,但没有函数

箭头有签名

Arrow a => a b c

可能不如中缀明确

Arrow (~>) => b ~> c

但你应该仍然认为它与b -> m c一样。

主要区别在于b -> m c使用b作为函数的参数,并且可以使用它执行您喜欢的操作,例如if b == "war" then launchMissiles else return ()但是您可以使用箭头' t(除非它是一个ArrowApply - 请参阅this question了解为什么ArrowApply为你提供Monad功能) - 一般,箭头只是执行它所做的事情并且无法根据数据,有点像Applicative。

将Monads转换为箭头

b -> m c的问题在于,你不能在实例声明中部分地应用它来从中间获取-> m位,所以假设b -> m c被称为Kleisli箭头Control.Monad定义(>>>),以便在完成所有包装和展开后,您获得f >>> g = \x -> f x >>= g - 但这相当于(>>>) = (>=>)。 (实际上,(.)是为类别定义的,而不是前向合成>>>,但我确实说过等价!)

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

你的例子,最后

(尽量忽略所有KleislirunKleisli - 它们只是包装和展开monadic值 - 当你定义自己的箭头时,它们不是必需的。)

如果我们展开对Maybe的意义,我们就会得到相当于作曲

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

和应用(纯)函数的Arrow方法是arr :: Arrow (~>) => (b -> c) -> b ~> c

我会将(~->)修改为Kleisli Maybe,以便您可以看到它的实际效果:

{-# LANGUAGE TypeOperators #-}
import Control.Arrow
type (~->) = Kleisli Maybe

g :: Integer ~-> Integer
g = Kleisli Just >>> arr (*2)

ghci> runKleisli g 10
Just 20

do符号类似,但输入和输出。 (GHC)

GHC实现了相当于do符号,proc表示法,可以让你做到

output <- arrow -< input

您已经习惯output <- monad,但现在有arrow -< input符号。与Monads一样,您不会在最后一行执行<-,也不会在proc表示法中执行此操作。

让我们使用Maybe版本的tail并从safe读取来说明符号(并宣传safe)。

{-# LANGUAGE Arrows #-}
import Control.Arrow
import Safe

this = proc inputList -> do
    digits <- Kleisli tailMay -< inputList
    number <- Kleisli readMay -<< digits
    arr (*10) -<< number

注意我使用了-<<的{​​{1}}变体,它允许您将输出作为输入,方法是将-<左侧的内容放入{{1}右侧的范围内}}。

显然<-相当于-<,但它只是(!)给你的想法。

this

所有Kleisli tailMay >>> Kleisli readMay >>> arr (*10)

就像我说的,如果我们没有输入,我们会使用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 ,就像我们在Monad中一样,如果我们不需要输出任何内容,则返回它。

您也会在()符号示例中看到()

()

答案 1 :(得分:8)

首先,我们需要一个与Maybe monad具有相同语义的箭头。我们可以从头开始定义它,但最简单的方法是将Maybe monad包装到Kleisli中:

type MaybeArrow = Kleisli Maybe

然后我们还需要一种方法来运行这个monad来提取结果:

runMaybeArrow :: MaybeArrow () a -> Maybe a
runMaybeArrow = flip runKleisli ()

另外,如何从给定值(只是忽略其输入)创建一个常量箭头也很方便:

val :: (Arrow a) => c -> a b c
val = arr . const

最后,我们得到:

g' = runMaybeArrow (val 5 >>> arr f)