9

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

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

并且gJust 10

我该如何做上述但使用箭头符号?

4

2 回答 2

11

将你的 Monad 思维转变为 Arrow 思维

翻译成 Arrow 的第一步是从m b独立思考转向思考a -> m b

有了一个单子,你会写

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

thing = doThis >>= use

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

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

然后使用(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m cfrom Control.Monaddo have

thing' = doThis' >=> use

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

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

没关系,如果你的 monad 不产生任何东西(比如 putStrLn 不产生任何东西),这只是一个共同问题,那么你只需要把它变成return ().

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

doThis () = 做 .... ...

这样任何东西都有签名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 ()使用箭头一样,但您不能(除非它是 ArrowApply - 请参阅此问题,了解为什么 ArrowApply 为您提供 Monad 功能) -一般来说,箭头只是做它所做的事情,而不是根据数据进行切换操作,有点像 Applicative 做的事情。

将 Monad 转换为箭头

问题b -> m c在于,您不能在实例声明中部分应用它以-> m从中间获取位,因此假设它b -> m c被称为 Kleisli 箭头,Control.Monad定义(>>>)为在所有包装和展开之后,您会得到f >>> g= \x -> f x >>= g- 但这相当于(>>>) = (>=>)。(实际上(.)是为 Categories 定义的,而不是 forwards composition >>>,但我确实说等价!)

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- 它们只是包装和展开单子值 - 当您定义自己的箭头时,它们不是必需的。)

如果我们解开这对 的含义Maybe,我们会得到等价于 compose

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

并且应用(纯)函数的箭头方式是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 实现了等价的donotation,procnotation,它可以让你做

output <- arrow -< input

你已经习惯了,output <- monad但现在有了arrow -< input符号。就像 Monads 一样,你不会<-在最后一行做,你也不会在proc符号中做。

让我们使用 tail 和 read from safe的 Maybe 版本来说明符号(和做广告safe)。

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

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

请注意,我使用了 的-<<变体-<,它允许您通过将 左侧的内容<-带入 右侧的范围来使用输出作为输入-<

显然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 中所做的那样,如果我们不需要输出任何东西,就返回它。

您还将()proc符号示例中看到:

thing = proc x -> do
     this <- thing1 -< ()
     () <- thing2 -< x
     returnA -< this
于 2014-02-16T16:40:59.683 回答
8

Maybe首先,我们需要一个与monad具有相同语义的箭头。我们可以从头开始定义它,但最简单的方法是将Maybemonad 包装成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)
于 2014-02-16T09:41:05.363 回答