考虑以下函数,从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实现func7
和func9
的原因。 monad / applicative / functor法如何适合上述推理?
答案 0 :(得分:4)
我不认为类别法是你在这里需要担心的;事实上,如果您的目的是了解非严格性,我认为类型类不必要地使练习复杂化。
这是一个更简单的示例,其中所有内容都是单态的,而不是使用底部给出示例,我们将在GHCi中使用:sprint
来观察评估的范围。
我的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。
我的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非常适合func6
与func7
。 (简而言之,不同之处在于,由于懒惰,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 a
,Functor
和Applicative
之间移动时获得(和失去)的内容。