dam*_*uar 8 functional-programming scala
我理解功能编程本身的不同概念:副作用,不变性,纯函数,参考透明度.但我无法将它们连接在一起.例如,我有以下问题:
ref之间的关系是什么?透明度和不变性.有人暗示另一个吗?
有时副作用和不变性可互换使用.这是对的吗?
这个问题需要一些特别挑剔的答案,因为它是关于定义常用词汇.
首先,函数是输入"域"与输出"范围"(或密码域)之间的一种数学关系.每个输入都会产生明确的输出.例如,整数加法函数+接受域中的输入Int x Int并生成该范围内的输出Int.
object Ex0 {
def +(x: Int, y: Int): Int = x + y
}
Run Code Online (Sandbox Code Playgroud)
给定任意值x和y,显然+总是会产生相同的结果.这是一个功能.如果编译器非常聪明,它可以插入代码来为每对输入缓存此函数的结果,并执行缓存查找作为优化.这显然是安全的.
问题是在软件中,术语"函数"有些滥用:尽管函数接受参数并返回其签名中声明的值,但它们也可以读取和写入某些外部上下文.例如:
class Ex1 {
def +(x: Int): Int = x + Random.nextInt
}
Run Code Online (Sandbox Code Playgroud)
我们不能再将其视为数学函数,因为对于给定的值x,+可以产生不同的结果(取决于随机值,它不会出现在+签名中的任何位置).如上所述,+无法安全地缓存结果.所以现在我们有一个词汇问题,我们通过说这Ex0.+是纯粹的,而Ex1.+不是.
好的,既然我们现在已经接受了一定程度的杂质,我们需要确定我们正在谈论的是什么样的杂质!在这种情况下,我们说不同的是,我们可以缓存Ex0.+"与它的输入相连的S结果x和y,而且我们不能缓存Ex1.+"与它的输入相连的S结果x.我们用来描述可缓存性的术语(或者更确切地说,函数调用与其输出的可替换性)是引用透明性.
所有纯函数都是引用透明的,但是一些引用透明的函数并不纯粹.例如:
object Ex2 {
var lastResult: Int
def +(x: Int, y: Int): Int = {
lastResult = x + y
lastResult
}
}
Run Code Online (Sandbox Code Playgroud)
这里,我们没有从任何外部环境,以及所产生的价值阅读Ex2.+的任何输入x,并y 会永远缓存,如Ex0.这是参考透明的,但它确实有副作用,即存储函数计算的最后一个值.其他人可以稍后再来抢lastResult,这会让他们对发生的事情有一些偷偷摸摸的洞察力Ex2.+!
旁注:你也可以说,
Ex2.+是不是引用透明,因为虽然缓存是关于函数的结果安全,副作用在高速缓存的情况下被自动忽略"命中".换句话说,如果副作用很重要,那么引入缓存会改变程序的含义(因此Norman Ramsey的评论)!如果您更喜欢这个定义,那么函数必须是纯的,以便引用透明.
现在,要注意的一件事是,如果我们Ex2.+使用相同的输入连续调用两次或更多次,lastResult则不会更改.调用方法n次的副作用相当于只调用一次方法的副作用,所以我们说这Ex2.+是幂等的.我们可以改变它:
object Ex3 {
var history: Seq[Int]
def +(x: Int, y: Int): Int = {
result = x + y
history = history :+ result
result
}
}
Run Code Online (Sandbox Code Playgroud)
现在,每次调用时Ex3.+,历史都会发生变化,因此函数不再是幂等的.
好的,到目前为止的回顾:纯函数是既不读取也不写入任何外部上下文的函数.它既具有参考透明性,又无副作用.从某些外部上下文读取的函数不再是引用透明的,而写入某些外部上下文的函数不再没有副作用.最后,当使用相同输入多次调用时具有与仅调用一次相同的副作用的函数称为幂等.请注意,没有副作用的函数(如纯函数)也是幂等的!
那么可变性和不变性如何发挥作用呢?好了,回头看Ex2和Ex3.他们引入了可变的vars.副作用是Ex2.+和Ex3.+他们各自的vars 变异!所以可变性和副作用是相辅相成的; 仅对不可变数据起作用的函数必须是无副作用的.它可能仍然不是纯粹的(也就是说,它可能不是引用透明的),但至少它不会产生副作用.
对此的逻辑后续问题可能是:"纯粹的功能风格有什么好处?" 该问题的答案更为复杂;)
对第一个“不” - 一个意味着另一个,但反之则不然,对第二个有条件的“是”。
“如果表达式可以用其值替换而不改变程序的行为,则称该表达式是引用透明的”。
不可变输入表明表达式(函数)将始终计算为相同的值,因此是引用透明的。
然而,(mergeconflict 在这一点上善意地纠正了我)引用透明并不一定需要不变性。
根据定义,副作用是函数的一个方面;这意味着当你调用一个函数时,它会改变一些东西。
不变性是数据的一个方面;它无法改变。调用这样的函数确实意味着不会有副作用。(在 Scala 中,这仅限于“不更改不可变对象”——开发人员有责任和决定)。
虽然副作用和不变性并不意味着同一件事,但它们是函数和函数所应用的数据密切相关的方面。
由于 Scala 不是一种纯函数式编程语言,因此在考虑“不可变输入”等语句的含义时必须小心 - 函数的输入范围可能包括除作为参数传递的元素之外的元素。类似地考虑副作用。