17

问这个问题我感觉很傻,但我一直在想这个问题,但我找不到任何答案。

所以问题是:为什么应用函子可以有副作用,但函子不能?

也许他们可以,而我只是从未注意到...?

4

4 回答 4

31

Functors 没有效果是不正确的。每个Applicative(和每个Monadthrough WrappedMonad)都是一个Functor. 主要区别在于它ApplicativeMonad您提供了如何使用这些效果、如何组合它们的工具。大致

  • 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实例。

于 2013-01-29T10:57:04.187 回答
25

这个答案有点过于简单化了,但是如果我们将副作用定义为受先前计算影响的计算,很容易看出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

它只是链接两个计算,丢弃第一个计算的最终结果(但可能应用“副作用”)。

所以简而言之,它是链接计算的能力,这是对计算中可变状态等效果的最低要求。

于 2013-01-29T10:15:41.290 回答
3

这里的其他答案正确地表明,仿函数不允许副作用,因为它们不能组合或排序,这在很大程度上是正确的,但是有一种方法可以对仿函数进行排序:向内。

让我们写一个有限的 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 提供了一种清晰的构建方法。

于 2013-01-30T01:43:50.013 回答
2

让我们首先将副作用重命名为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在于它具有可能没有结果值的效果。但是你不能使用应用组合器来组合它。

于 2013-01-29T07:40:29.427 回答