functor、applicative 和 Monad 之间的交互

Interaction between functor, applicative and Monad

我是 Haskell 的新手,我正试图更好地理解仿函数、应用函数和 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

我想要实现的是 "piping" 通过 monad 的绑定来自 Functor/Applicative 的值,但我在 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

问题在这里:

myf :: FooBar Int -> FooBar Int

以上使用时会出现问题

something >>= myf

因为它要求 something 具有类型 FooBar (FooBar Int)。 这反过来又使数字常量成为 FooBar Int 而不是 Int 类型, (+)FooBar Int 类型的 "numbers" 进行操作。这会触发 输入错误。

也许你只是想使用

myf something

相反。在您的特定情况下,

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

由于您试图了解 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

同样的事情还违反了适用的法律

您不能像现在这样简单地丢弃值是用 Foo 还是 Bar 构造的信息。 由于 return = Foo,您需要确保 Foo 的行为与 "purely" 一致——即与其结合(在 (<*>)(>>=)) 不会改变另一个参数的结构。解决此问题的一种可能方法是始终进行 Bar "taint" 计算:

-- 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 同构。