我一次又一次读到有效这个词,但我仍然无法清楚地定义它的含义。我假设正确的上下文是有效的计算,但我也看到了有效的 values 这个词
我曾经认为有效意味着有副作用。但在Haskell中没有副作用(除了某种程度上IO)。整个地方仍有有效的计算。
然后我读到monad用于创建有效的计算。我可以在State
Monad的背景下对此有所了解。但我没有看到Maybe
monad中的任何副作用。一般来说,在我看来,Monads包含一个类似函数的东西比Monads更容易看到产生副作用,Monads只包装一个值。
谈到Applicative
仿函数,我更加迷失。我总是看到applicative functor作为map
一个带有多个参数的函数的方法。我在这里看不到任何副作用。或效果和与效果之间有区别吗?
答案 0 :(得分:22)
side effect是与其环境的可观察交互(除了计算其结果值)。在Haskell中,我们努力避免具有这种副作用的函数。这甚至适用于IO
操作:当评估IO
操作时,不会执行任何副作用,只有在IO
值中规定的操作在{{1}内执行时才执行它们}}
然而,当处理与组合计算有关的抽象时,例如应用仿函数和monad,可以方便地区分实际值和休息"我们经常呼叫"效果"。特别是,如果我们的main
类型为kind f
,那么在* -> *
中,f a
部分为"值为"什么"遗骸"是"效果"。
我故意引用这些术语,因为没有确切的定义(据我所知),它只是一个口语定义。在某些情况下,根本没有值或多个值。例如a
"效果"是Maybe
"效果"可能没有值(计算被中止)是有多个(或零)值。对于更复杂的类型,这种区别可能更加困难。
"效果之间的区别"和"价值观"并不真正依赖于抽象。 []
,Functor
和Applicative
只是向我们提供了我们可以用它们做的工具(Monad
允许修改内部值,Functor
s允许组合效果并且Applicative
允许效果取决于之前的值)。但是在Monad
s的背景下,创建一个正在发生的事情的心理图片会更容易一些,因为monadic动作可以"看到"上一次计算的结果值,如
Monad
运算符:第二个函数接收类型(>>=) :: m a -> (a -> m b) -> m b
的值,因此我们可以想象"先前的计算有一些效果,现在有结果值,我们可以用它来做某事"。
答案 1 :(得分:9)
为了支持Petr Pudlák's answer,这里有一个论点,关于那里所支持的“效果”这个更广泛概念的起源。
“有效编程”这一短语出现在McBride和Patterson的Applicative Programming with Effects摘要中,该论文介绍了应用函子:
在本文中,我们介绍了
Applicative
仿函数 - 一种有效编程的应用风格的抽象表征,比Monad
弱,因此更广泛。
“效果”和“有效”出现在该论文的其他一些段落中;这些事件被认为是不起眼的,不需要明确澄清。例如,在Applicative
的定义出现之后(第3页):
在每个示例中,都有一个嵌入通常的类型构造函数
f
价值观,但支持其自己特殊的方式赋予通常的应用语言[...]我们相应地介绍Applicative
类:[Haskell对
Applicative
]的定义此课程概括 S 和 K [即显示在
Reader
/ functionApplicative
实例中的the S and K combinators从一个环境线程化到一般的线程效果。
从这些引用中,我们可以推断出,在这种背景下:
效果是Applicative
线程“一般”的事情。
效果与给定Applicative
个实例的类型构造函数相关联。
Monad
也处理效果。
根据这些线索,我们可以将“效果”的这种用法追溯到至少Wadler's papers on monads。例如,以下是 Monads的第6页引用函数式编程:
通常, a→b 类型的函数被类型 a的函数替换 →M b 。这可以作为接受 a 类型的参数的函数读取 并返回 b 类型的结果,并捕获可能的附加效果 中号。这种效果可能是对状态,生成输出,引发异常或者你有什么行为。
从同一篇论文,第21页:
如果monads封装效果并且列表形成monad,那么列表是否对应于某些效果?确实他们这样做,他们所对应的效果就是选择。可以将 [a] 类型的计算视为提供值的选择,每个元素对应一个列表。 a→b 类型函数的monadic等效函数是 a→[b] 类型的函数。
这里的“对应一些效果”这句话是关键。它与摘要中更直接的主张有关:
Monads为模拟其他语言中的效果提供了一个方便的框架,例如全局状态,异常处理,输出或非确定性。
音调是monad可以用来表达那些在“其他语言”中通常被编码为副作用的东西 - 就像PetrPudlák在这里所说的那样,“与[的可观察的交互]一个函数的环境(除了计算其结果值)“。通过转喻,这很容易导致“效果”获得第二个含义,比“副作用”更广泛 - 即通过类型构造函数引入的任何内容,即Monad
实例。随着时间的推移,这个含义进一步推广到涵盖其他仿函数类,如Applicative
,如McBride和Patterson的作品中所见。
总之,我认为“效果”在Haskell的说法中有两个合理的含义:
“文字”或“绝对”的:效果是副作用;以及
“一般化”或“相对”一种:效果是一种函数上下文。
有时,当涉及的每一方隐含地假设“效果”的不同含义时,就会发生可避免的术语分歧。另一个可能的争论点涉及单独处理Functor
时是否合理地谈论效果,而不是像Applicative
或Monad
这样的子类(我认为这样做是可以的) ,同意Petr Pudlák's answer to Why can applicative functors have side effects, but functors can't?)。
答案 2 :(得分:4)
在我看来,“副作用”是正常功能无法做到的任何事情。换句话说,除了只返回一个值之外的任何东西。
考虑以下代码块:
let
y = foo x
z = bar y
in foobar z
这会调用foo
,然后调用bar
,然后调用foobar
三个普通函数。很简单吧?现在考虑一下:
do
y <- foo x
z <- bar y
foobar z
这也调用了三个函数,但它也在每对行之间无形地调用(>>=)
。这意味着会发生一些奇怪的事情,具体取决于函数运行的monad类型。
如果这是身份monad,没有什么特别的事情发生。 monadic版本与纯版本完全相同。没有副作用。
如果每个函数都返回Maybe
- 那么,如果(例如)bar
返回Nothing
,则整个代码块将中止。正常功能不能这样做。 (即,在纯版本中,无法阻止foobar
被调用。)所以这个版本做了纯版本不能做的事情。每个函数都可以返回值或中止块。这是副作用。
如果每个函数返回一个列表,那么代码将针对所有可能的结果组合执行。同样,在纯版本中,无法使任何函数使用不同的参数执行多次。这就是副作用。
如果每个函数都在状态monad中运行,那么(例如)foo
可以将一些数据直接发送到foobar
,除了您可以看到传递的值bar
1}}。同样,你不能用纯函数做到这一点,所以这是一个副作用。
在IO
monad中,你有各种有趣的效果。您可以将文件保存到磁盘(文件基本上是一个巨大的全局变量),甚至可以影响在其他计算机上运行的代码(我们称之为网络I / O)。
ST
monad是IO
monad的缩减版。它允许可变状态,但自包含的计算不能相互影响。
STM
monad允许多个线程相互通信,并且可能导致代码多次执行,并且......好吧,你不能用正常的函数做任何这些。< / p>
延续monad允许你打破人们的思想!可以说纯粹的功能可以实现......
答案 3 :(得分:0)
“效果是一个非常模糊的术语,这是可以的,因为我们正在尝试谈论语言之外的东西。效果和副作用不是同一回事。效果是好的。副作用是错误。
他们的词汇相似性确实是不幸的,因为它导致许多人在阅读这些思想时混淆了这些思想,并且人们使用一种思想代替了另一种思想,因此引起了很多困惑。”
更多信息,请参见此处:https://www.slideshare.net/pjschwarz/rob-norrisfunctionalprogrammingwitheffects