我正在尝试理解箭头符号,特别是它如何与Monads一起使用。使用Monads,我可以定义以下内容:
f = (*2)
g = Just 5 >>= (return . f)
且g
为Just 10
如何使用箭头符号进行上述操作?
答案 0 :(得分:10)
转换为箭头的第一步是从单独考虑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。
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))
(尽量忽略所有Kleisli
和runKleisli
- 它们只是包装和展开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)