为什么我不能仅使用 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 来实现 func7
和 func9
的原因。 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
结果。
很好地涵盖了 func6
和 func7
。 (简而言之,不同之处在于,由于惰性,func6 @Maybe
可以决定用于结果的构造函数应该是 Just
还是 Nothing
而无需实际查看其内部的任何值争论。)
至于 func9
,Monad
的必要性在于该函数涉及使用 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 (>>=)
传递给fmap
的a -> b
函数没有关于f
的信息,所涉及的Functor
,所以fmap
根本无法改变效果. (<*>)
可以改变效果,但只能通过组合它的两个参数的效果——可能在 f (a -> b)
参数中找到的 a -> b
函数对此没有任何影响。但是,对于 (>>=)
,a -> f b
函数精确地用于从 f a
参数中找到的值生成效果。
我建议 Difference between Monad and Applicative in Haskell 进一步阅读你在 Functor
、Applicative
和 Monad
之间移动时获得(和失去)的东西。
考虑以下取自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 来实现 func7
和 func9
的原因。 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
结果。
func6
和 func7
。 (简而言之,不同之处在于,由于惰性,func6 @Maybe
可以决定用于结果的构造函数应该是 Just
还是 Nothing
而无需实际查看其内部的任何值争论。)
至于 func9
,Monad
的必要性在于该函数涉及使用 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 (>>=)
传递给fmap
的a -> b
函数没有关于f
的信息,所涉及的Functor
,所以fmap
根本无法改变效果. (<*>)
可以改变效果,但只能通过组合它的两个参数的效果——可能在 f (a -> b)
参数中找到的 a -> b
函数对此没有任何影响。但是,对于 (>>=)
,a -> f b
函数精确地用于从 f a
参数中找到的值生成效果。
我建议 Difference between Monad and Applicative in Haskell 进一步阅读你在 Functor
、Applicative
和 Monad
之间移动时获得(和失去)的东西。