仿函数,应用程序和Monad之间的交互

时间:2015-04-04 15:19:40

标签: haskell monads functor applicative

我是Haskell的新手,我正在努力更好地理解functor,applicative和monad是如何协同工作的。在我的例子中:

import Control.Monad
import Control.Applicative

data FooBar a = Foo a | Bar a deriving (Show)

myf :: FooBar Int -> FooBar Int
myf (Bar a) = Foo (a * 10)
myf (Foo a) = Bar (a * 10)

instance Functor FooBar where
    fmap func (Foo val) = Bar (func val)
    fmap func (Bar val) = Foo (func val)

instance Applicative FooBar where
    pure = Foo
    (Foo f) <*> (Foo x) = Foo (f x)
    (Foo f) <*> (Bar x) = Foo (f x)
    (Bar f) <*> (Foo x) = Bar (f x)
    (Bar f) <*> (Bar x) = Bar (f x)

instance Monad FooBar where
    return = Foo
    (Foo x) >>= f = f x
    (Bar x) >>= f = f x

main = putStrLn $ show $ Foo (+3) <*> Foo 5 >>= myf

我想要实现的是&#34;管道&#34; Funator / Applicative通过monad绑定的值,但我在main行中出错:

ghc: No instance for (Num (FooBar Int)) arising from a use of `+'
Possible fix: add an instance declaration for (Num (FooBar Int))
In the first argument of `Foo', namely `(+ 3)'
In the first argument of `(<*>)', namely `Foo (+ 3)'
In the first argument of `(>>=)', namely `Foo (+ 3) <*> Foo 5'

如果我用这样的Functor替换Applicative,会发生类似的事情:

main = putStrLn $ show $ (+3) <$> Foo 5 >>= myf

实际上我可能会做什么,或者我的定义中有错误?

修改 这是一个更清洁的解决方案:

import Control.Monad
import Control.Applicative

data FooBar a = Foo a | Bar a deriving (Show)

myf :: Int -> FooBar Int
myf (a) = return (a * 10)

instance Functor FooBar where
    fmap func (Foo val) = Foo (func val)
    fmap func (Bar val) = Bar (func val)

instance Applicative FooBar where
    pure = Foo
    (Foo f) <*> something = fmap f something
    (Bar f) <*> something = fmap f something

instance Monad FooBar where
    return = Foo
    (Foo x) >>= f = f x
    (Bar x) >>= f = f x

main = putStrLn $ show $ (+) <$> Bar(19) <*> (Foo 3) >>= myf

2 个答案:

答案 0 :(得分:5)

问题在于:

myf :: FooBar Int -> FooBar Int

使用

时会出现上述问题
something >>= myf

因为something需要FooBar (FooBar Int)类型。 这反过来使数字常量为FooBar Int而不是Int, 并(+)对&#34;数字进行操作&#34;类型为FooBar Int。这触发了 类型错误。

也许您只想使用

myf something

代替。在您的特定情况下,

main = putStrLn $ show $ myf $ Foo (+3) <$> Foo 5

答案 1 :(得分:4)

由于您试图了解FunctorApplicativeMonad之间的关系,因此您可能想知道您的MonadApplicative个实例是不相容的。 (<*>)的行为必须与Control.Monad.ap的行为相同:

ap :: (Monad m) => m (a -> b) -> m a -> m b
ap mf mx = mf >>= (\f -> mx >>= (\x -> return (f x)))

但你有:

Bar id <*>  Bar 0 = Bar 0
Bar id `ap` Bar 0 = Foo 0

事实上,造成这种情况的原因还在于违反了monad法律:

m >>= return = m

但你有

Bar 0 >>= return = Foo 0

同样的事情也导致违反的适用法律。

您不能简单地丢弃价值是否以FooBar的方式构建的信息。 return = Foo 以来,您需要确保Foo“纯粹”行事 - 即与(<*>)(>>=)合并不改变其他参数的结构。一种可能的方法是始终让Bar“污染”计算:

-- Since we have a Foo, we need to preserve whatever f does:
Foo x >>= f = f x
Bar x >>= f = case f x of   
                  -- If f x returned a Foo, we need to preserve Bar from the left arg:
                  Foo y -> Bar y
                  -- We can do whatever we like with this clause:
                  Bar y -> Bar y

然后使用ap找出您的Applicative实例应该是什么。有趣的是,这现在与Writer Any同构。