为什么我不能仅使用 Functor/Applicative 约束来实现这些功能?

Why can't I implement these functions with a Functor/Applicative constraint only?

考虑以下取自the answers to this problem set的函数:

func6 :: Monad f => f Integer -> f (Integer,Integer)
func6 xs = do
    x <- xs
    return $ if x > 0 then (x, 0)
                      else (0, x)

func6' :: Functor f => f Integer -> f (Integer,Integer)
-- slightly unorthodox idiom, with an partially applied fmap
func6' = fmap $ \x -> if x > 0 then (x,0) else (0,x)

-- func7 cannot be implemented without Monad if we care about the precise
-- evaluation and layzness behaviour:
-- > isJust (func7 (Just undefined))
-- *** Exception: Prelude.undefined
--
-- If we care not, then it is equivalent to func6, and there we can. Note that
-- > isJust (func6 (Just undefined))
-- True    
func7 :: Monad f => f Integer -> f (Integer,Integer)
func7 xs = do
    x <- xs
    if x > 0 then return (x, 0)
             else return (0, x)

-- func9 cannot be implemented without Monad: The structure of the computation
-- depends on the result of the first argument.
func9 :: Monad f => f Integer -> f Integer -> f Integer -> f Integer
func9 xs ys zs = xs >>= \x -> if even x then ys else zs

虽然我理解 func7 的反例,但我不明白为什么我们可以只使用 monad 来实现 func7func9 的原因。 monad/applicative/functor 定律如何符合上述推理?

我认为类型类法则不是您在这里需要担心的;事实上,如果你的目的是理解非严格性,我认为类型类不必要地使练习复杂化。

这是一个更简单的示例,其中所有内容都是单态的,而不是使用 bottom 给出示例,我们将在 GHCi 中使用 :sprint 来观察评估的范围。

func6

我这里的x6例子对应问题中的func6

λ> x6 = Just . bool 'a' 'b' =<< Just True

最初,没有任何评估。

λ> :sprint x6
x6 = _

现在我们评估'isJust x6'。

λ> isJust x6
True

现在我们可以看到 x6 已被部分评估。不过,只有它的头部。

λ> :sprint x6
y = Just _

为什么?因为不需要知道 bool 'a' 'b' 部分的结果就可以确定 Maybe 是否会成为 Just。所以它仍然是一个未评估的thunk。

func7

我这里的x7例子对应问题中的func7

λ> x7 = bool (Just 'a') (Just 'b') =<< Just True
x :: Maybe Char

同样,最初没有任何评估。

λ> :sprint x7
x = _

我们将再次申请 isJust

λ> isJust x7
True

在这种情况下,Just 的内容确实得到了评​​估(所以我们说这个定义是 "more strict" 或 "not as lazy")。

λ> :sprint x7
x = Just 'b'

为什么?因为我们必须先评估 bool 应用程序,然后才能判断它是否会产生 Just 结果。

很好地涵盖了 func6func7。 (简而言之,不同之处在于,由于惰性,func6 @Maybe 可以决定用于结果的构造函数应该是 Just 还是 Nothing 而无需实际查看其内部的任何值争论。)

至于 func9Monad 的必要性在于该函数涉及使用 xs 中找到的值来决定结果的函数上下文。 (此设置中 "functorial context" 的同义词包括 "effects",并且正如您引用的解决方案所说,"structure of the computation"。)为了便于说明,请考虑:

func9 (fmap read getLine) (putStrLn "Even!") (putStrLn "Odd!")

比较fmap(<*>)(>>=)的类型很有用:

(<$>) :: Functor f     =>   (a ->   b) -> (f a -> f b) -- (<$>) = fmap
(<*>) :: Applicative f => f (a ->   b) -> (f a -> f b)
(=<<) :: Monad f       =>   (a -> f b) -> (f a -> f b) -- (=<<) = filp (>>=)

传递给fmapa -> b函数没有关于f的信息,所涉及的Functor,所以fmap根本无法改变效果. (<*>) 可以改变效果,但只能通过组合它的两个参数的效果——可能在 f (a -> b) 参数中找到的 a -> b 函数对此没有任何影响。但是,对于 (>>=)a -> f b 函数精确地用于从 f a 参数中找到的值生成效果。

我建议 Difference between Monad and Applicative in Haskell 进一步阅读你在 FunctorApplicativeMonad 之间移动时获得(和失去)的东西。