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

时间:2018-03-15 14:09:07

标签: haskell monads

考虑以下函数,从the answers到此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法如何适合上述推理?

2 个答案:

答案 0 :(得分:4)

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

这是一个更简单的示例,其中所有内容都是单态的,而不是使用底部给出示例,我们将在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的内容确实得到了评​​估(所以我们说这个定义是“更严格”或“不是懒惰”)。

λ> :sprint x7
x = Just 'b'

为什么呢?因为在我们判断它是否会产生bool结果之前,我们必须先评估Just应用程序。

答案 1 :(得分:1)

Chris Martin's answer非常适合func6func7。 (简而言之,不同之处在于,由于懒惰,func6 @Maybe可以决定用于结果的构造函数应该是Just还是Nothing而不必实际查看其中的任何值参数)。

至于func9,使Monad成为必要的是该函数涉及使用xs中找到的值来决定结果的函数上下文。 (此设置中“functorial context”的同义词包括“effects”,并且,正如您引用的解决方案所说的那样,“计算结构”。)为了便于说明,请考虑:

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 (>>=)

传递给a -> b的{​​{1}}函数没有关于fmap的信息,涉及f,因此Functor根本无法改变效果。 fmap可以改变效果,但只能通过组合其两个参数的效果 - (<*>)参数中可能找到的a -> b函数与此无关。但是,使用f (a -> b)时,(>>=)函数可以精确地用于根据a -> f b参数中的值生成效果。

我建议Difference between Monad and Applicative in Haskell进一步阅读在f aFunctorApplicative之间移动时获得(和失去)的内容。