我觉得这个问题很傻,但是我已经想了一会儿,我找不到任何答案。
所以问题是:为什么应用仿函数有副作用,但仿函数不能?
也许他们可以而且我从未注意到......?
答案 0 :(得分:26)
Functor
没有效果。每个Applicative
(以及每个Monad
到WrappedMonad
)都是Functor
。主要区别在于Applicative
和Monad
为您提供了如何使用这些效果的工具,以及如何将它们组合在一起。大致
Applicative
允许您对效果进行排序并将值组合在一起。Monad
还允许您根据前一个效果确定下一个效果。但是Functor
只允许你修改里面的值,它不会让工具对效果做任何事情。因此,如果某些内容只是Functor
而不是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
实例。
答案 1 :(得分: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
简单地将两个计算链接起来,丢弃第一个计算的最终结果 (但可能应用“副作用”)。
简而言之,它是链计算的能力,这是计算中可变状态等效果的最低要求。
答案 2 :(得分: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
当然,通过免费财产,这形成一个单子,但效果实际上来自&#34;层&#34;仿函数。
interpretColors :: ColorWriter a -> ([Color], a)
interpretColors (Pure a) = ([], a)
interpretColors (Free (Re next)) = let (colors, a) = interpretColors next
in (R : colors, a)
...
所以,这是一种伎俩。真的是&#34;计算&#34;正在由免费的 monad 引入,但计算的材料,隐藏的上下文,只是由一个仿函数引入。事实证明,您可以使用任何数据类型执行此操作,它甚至不需要是Functor,但Functor提供了一种明确的方法来构建它。
答案 3 :(得分:2)
让我们首先将副作用重命名为效果。各种价值观都有影响。仿函数是一种类型,允许您将函数映射到该效果产生的任何函数。
当仿函数不适用时,它不允许您使用某种构图样式来表示效果。让我们选择一个(人为的)例子:
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
,因为它可能没有结果值。但是你无法使用应用组合器来组合它。