请考虑以下代码:
transform :: Foo -> Bar
transform foo =
case foo of
Foo1 x -> Foo1 x
Foo2 x y -> Foo2 x (transform y)
Foo3 x y z -> Foo3 x (transform y) (transform z)
现在假设由于某种原因我改变它以在monad中工作(例如,因为我有状态我想随身携带或其他)。现在我们有了
transform :: Foo -> State Int Bar
transform foo =
case foo of
Foo1 x -> return $ Foo1 x
Foo2 x y -> do
y' <- transform y
return $ Foo2 x y'
Foo3 x y z -> do
y' <- transform y
z' <- transform z
return $ Foo3 x y' z'
嗯,所有都可以以及所有内容,但是......我们可以改进吗?我有一种唠叨的感觉,我应该能够定义一些漂亮的中缀功能,使它看起来更漂亮,但每次我试图弄清楚如何,我的思绪在一段时间后麻木了......
答案 0 :(得分:10)
你的直觉是正确的。这是ap
类中Monad
函数的作用,或者等同于<*>
类中的Applicative
运算符,几乎所有monad都实现了(并且实际上是将来成为Monad
的超类。
这是它的类型:
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
因此它基本上将包装函数a -> b
应用于包装a
以返回包装b
。它相当于:
mf <*> mx = do
f <- mf
x <- mx
return $ f x
以下是如何在您的案例中使用它,强调不同案例之间的相似性:
transform foo =
case foo of
Foo1 x -> return Foo1 <*> return x
Foo2 x y -> return Foo2 <*> return x <*> transform y
Foo3 x y z -> return Foo3 <*> return x <*> transform y <*> transform z
考虑到return f <*> return x == return (f x)
:
transform foo =
case foo of
Foo1 x -> return $ Foo1 x
Foo2 x y -> return (Foo2 x) <*> transform y
Foo3 x y z -> return (Foo3 x) <*> transform y <*> transform z
更进一步,使用与<$>
类相同的fmap
运算符Functor
:
transform foo =
case foo of
Foo1 x -> return $ Foo1 x
Foo2 x y -> Foo2 x <$> transform y
Foo3 x y z -> Foo3 x <$> transform y <*> transform z
答案 1 :(得分:2)
transform :: Foo -> State Int Bar
transform foo =
case foo of
Foo1 x -> return $ Foo1 x
Foo2 x y -> Foo2 x <$> transform y
Foo3 x y z -> Foo3 x <$> transform y <*> transform z
您的Monad需要Control.Applicative
和Functor
/ Applicative
个实例(它们适用于州,而其他Monads实施相对简单)。
答案 2 :(得分:0)
对于其他任何试图解决这个问题的人......
似乎关键的定义是这些:
mf <*> mx = do
f <- mf
x <- mx
return (f x)
f <$> mx = do
x <- mx
return (f x)
特别是,类型不同; <*>
mf
需要<$>
,f
需要(<*>) :: Monad m => m (x -> y) -> m x -> m y
(<$>) :: Monad m => (x -> y) -> m x -> m y
:
Monad
(当然不是,其中任何一种都是fmap
方法,甚至根本都不是。但是你明白了......)
作为一个从不使用{{1}}的人,这需要一段时间才能习惯......