使monadic代码更短

时间:2014-02-15 13:06:41

标签: haskell monads

请考虑以下代码:

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'

嗯,所有都可以以及所有内容,但是......我们可以改进吗?我有一种唠叨的感觉,我应该能够定义一些漂亮的中缀功能,使它看起来更漂亮,但每次我试图弄清楚如何,我的思绪在一段时间后麻木了......

3 个答案:

答案 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.ApplicativeFunctor / 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}}的人,这需要一段时间才能习惯......