问这个问题我感觉很傻,但我一直在想这个问题,但我找不到任何答案。
所以问题是:为什么应用函子可以有副作用,但函子不能?
也许他们可以,而我只是从未注意到...?
问这个问题我感觉很傻,但我一直在想这个问题,但我找不到任何答案。
所以问题是:为什么应用函子可以有副作用,但函子不能?
也许他们可以,而我只是从未注意到...?
Functor
s 没有效果是不正确的。每个Applicative
(和每个Monad
through WrappedMonad
)都是一个Functor
. 主要区别在于它Applicative
为Monad
您提供了如何使用这些效果、如何组合它们的工具。大致
Applicative
允许您对效果进行排序并在其中组合值。Monad
此外,您还可以根据前一个效果的结果确定下一个效果。但是Functor
只允许你修改里面的值,它不给工具做任何效果。因此,如果某事是 justFunctor
和 not Applicative
,并不意味着它没有效果。它只是没有一种机制如何以这种方式组合它们。
更新:例如,考虑
import Control.Applicative
newtype MyF r a = MyF (IO (r, a))
instance Functor (MyF r) where
fmap f (MyF x) = MyF $ fmap (fmap f) x
这显然是一个Functor
带有效果的实例。只是我们没有办法定义符合这些效果的操作 Applicative
。除非我们对 施加一些额外的约束r
,否则无法定义Applicative
实例。
这个答案有点过于简单化了,但是如果我们将副作用定义为受先前计算影响的计算,很容易看出Functor
类型类不足以解决副作用,因为没有办法链接多个计算。
class Functor f where
fmap :: (a -> b) -> f a -> f b
函子唯一能做的就是通过一些纯函数来改变计算的最终结果a -> b
。
但是,应用函子添加了两个新函数,pure
并且<*>
.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
这<*>
是这里的关键区别,因为它允许我们链接两个计算:(
f (a -> b)
产生函数f a
的计算)和提供应用函数的参数的计算。使用pure
并且<*>
可以定义例如
(*>) :: f a -> f b -> f b
它只是链接两个计算,丢弃第一个计算的最终结果(但可能应用“副作用”)。
所以简而言之,它是链接计算的能力,这是对计算中可变状态等效果的最低要求。
这里的其他答案正确地表明,仿函数不允许副作用,因为它们不能组合或排序,这在很大程度上是正确的,但是有一种方法可以对仿函数进行排序:向内。
让我们写一个有限的 Writer 函子。
data Color = R | G | B
data ColorW a = Re a | Gr a | Bl a deriving (Functor)
然后对其应用 Free monad 类型
data Free f a = Pure a | Free (f (Free f a))
liftF :: Functor f => f a -> Free f a
liftF = Free . fmap Pure
type ColorWriter = Free ColorW
red, blue, green :: a -> ColorWriter a
red = liftF . Re
green = liftF . Gr
blue = liftF . Bl
当然,通过 free 属性,这形成了一个 monad,但效果确实来自函子的“层”。
interpretColors :: ColorWriter a -> ([Color], a)
interpretColors (Pure a) = ([], a)
interpretColors (Free (Re next)) = let (colors, a) = interpretColors next
in (R : colors, a)
...
所以,这是一种诡计。真正的“计算”是由自由单子引入的,但计算的材料,隐藏的上下文,只是由一个函子引入的。事实证明,您可以使用任何数据类型执行此操作,它甚至不必是 Functor,但 Functor 提供了一种清晰的构建方法。
让我们首先将副作用重命名为effects。各种价值观都会产生影响。仿函数是一种允许您将函数映射到由该效果产生的任何内容的类型。
当函子不适用时,它不允许您为效果使用某种组合风格。让我们选择一个(人为的)示例:
data Contrived :: * -> * where
AnInt :: Int -> Contrived Int
ABool :: Bool -> Contrived Bool
None :: Contrived a
这很容易成为一个函子:
instance Functor Contrived where
fmap f (AnInt x) = AnInt (f x)
fmap f (ABool x) = ABool (f x)
fmap _ None = None
但是,对于 没有合理的实现pure
,所以这种类型不是应用函子。相似之处Maybe
在于它具有可能没有结果值的效果。但是你不能使用应用组合器来组合它。