Scala的“副作用”是什么?
我目前正在学习使用Scala进行函数式编程 我也在学习循环以及如何避免由于副作用而产生的循环Scala的“副作用”是什么?,scala,functional-programming,side-effects,Scala,Functional Programming,Side Effects,我目前正在学习使用Scala进行函数式编程 我也在学习循环以及如何避免由于副作用而产生的循环 这意味着什么?作为@Jörg的另一个例子,使用Scala中编写的命令式语言,以这个简单的循环为例: def printUpTo(limit: Int): Unit = { var i = 0 while(i <= limit) { println("i = " + i) i += 1 // in another part of the loo
这意味着什么?作为@Jörg的另一个例子,使用Scala中编写的命令式语言,以这个简单的循环为例:
def printUpTo(limit: Int): Unit = {
var i = 0
while(i <= limit)
{
println("i = " + i)
i += 1
// in another part of the loop
if (i % 5 == 0) { i += 1 } // ops. We should not evaluate "i" here.
}
}
我们可以仅使用foreach使代码更加清晰:
和更小的
def printUpToFunc3(limit: Int): Unit = (0 to limit).foreach(println)
作为@Jörg的另一个例子,我们使用Scala编写的命令式语言来实现这个简单的循环:
def printUpTo(limit: Int): Unit = {
var i = 0
while(i <= limit)
{
println("i = " + i)
i += 1
// in another part of the loop
if (i % 5 == 0) { i += 1 } // ops. We should not evaluate "i" here.
}
}
我们可以仅使用foreach使代码更加清晰:
和更小的
def printUpToFunc3(limit: Int): Unit = (0 to limit).foreach(println)
纯函数式编程语言中的函数与数学中的函数完全相同:它们基于参数值生成结果值,并且仅基于参数值 通常称为效应的副作用是其他一切。也就是说,所有没有读取参数并返回结果的内容都是副作用 这包括但不限于: 突变状态, 从控制台读取输入, 将输出打印到控制台, 读取、创建、删除或写入文件, 从网络上阅读或写作, 反射 根据当前时间, 启动或中止线程或进程,或 任何类型的I/O,最重要的是 调用不纯函数。 最后一点非常重要:调用一个不纯函数会使函数变得不纯。从这个意义上说,副作用具有传染性 请注意,只允许阅读参数的说法有些简化。一般来说,我们认为函数的环境也是一种不可见的参数。这意味着,例如,一个闭包可以从它关闭的环境中读取变量。允许函数读取全局变量 Scala是一种面向对象的语言,它的方法有一个不可见的参数,允许读取该参数 这里的重要属性称为引用透明度。如果可以在不改变程序含义的情况下用其值替换函数或表达式,则函数或表达式是引用透明的,反之亦然 注意,通常,术语纯或纯功能性、参考透明和无副作用可互换使用 例如,在下面的程序中,子表达式2+3是引用透明的,因为我可以用它的值5替换它,而不改变程序的含义: println2+3 与…的意思完全相同 println5 但是,println方法在引用上不是透明的,因为如果我用它的值替换它,程序的含义就会改变: println2+3 不具有与相同的含义 它只是以单位表示的值,是println的返回值 这样做的结果是,当传递相同的参数时,引用透明函数总是返回相同的结果值。对于所有代码,您应该为相同的输入获得相同的输出。或者更一般地说,如果你一次又一次地做同样的事情,同样的结果应该一次又一次地发生 这就是循环和副作用之间的联系所在:循环一遍又一遍地做着同样的事情。所以,它应该一次又一次地得到相同的结果。但事实并非如此:它至少会有一次不同的结果,即它将完成。除非它是一个无限循环
为了使循环有意义,它们必须有副作用。然而,一个纯粹的功能性程序不能有副作用。因此,循环在纯函数程序中不可能有意义。纯函数编程语言中的函数与数学中的函数完全相同:它们根据参数值生成结果值,并且仅基于参数值 通常称为效应的副作用是其他一切。也就是说,所有没有读取参数并返回结果的内容都是副作用 这包括但不限于: 突变状态, 从控制台读取输入, 将输出打印到控制台, 读取、创建、删除或写入文件, 从网络上阅读或写作, 反射 根据当前时间, 启动或中止线程或进程,或 任何类型的I/O,最重要的是 调用不纯函数。 最后一点非常重要:调用一个不纯函数会使函数变得不纯。从这个意义上说,副作用具有传染性 请注意,只允许阅读参数的说法有些简化。一般来说,我们认为函数的环境也是一种不可见的参数。这意味着,例如,一个闭包可以从它关闭的环境中读取变量。允许函数读取全局变量 Scala是一种面向对象的语言,它的方法有一个不可见的参数,允许读取该参数。 这里的重要属性称为引用透明度。如果可以在不改变程序含义的情况下用其值替换函数或表达式,则函数或表达式是引用透明的,反之亦然 注意,通常,术语纯或纯功能性、参考透明和无副作用可互换使用 例如,在下面的程序中,子表达式2+3是引用透明的,因为我可以用它的值5替换它,而不改变程序的含义: println2+3 与…的意思完全相同 println5 但是,println方法在引用上不是透明的,因为如果我用它的值替换它,程序的含义就会改变: println2+3 不具有与相同的含义 它只是以单位表示的值,是println的返回值 这样做的结果是,当传递相同的参数时,引用透明函数总是返回相同的结果值。对于所有代码,您应该为相同的输入获得相同的输出。或者更一般地说,如果你一次又一次地做同样的事情,同样的结果应该一次又一次地发生 这就是循环和副作用之间的联系所在:循环一遍又一遍地做着同样的事情。所以,它应该一次又一次地得到相同的结果。但事实并非如此:它至少会有一次不同的结果,即它将完成。除非它是一个无限循环
为了使循环有意义,它们必须有副作用。然而,一个纯粹的功能性程序不能有副作用。因此,循环在纯功能程序中不可能有意义。所有这些都是很好的答案。如果你来自另一种语言,只需快速添加一点即可 空洞函数 函数不返回任何内容,例如void,这意味着存在副作用 例如,如果您有c语言的代码
void Log (string message) => Logger.WriteLine(message);
这会产生副作用,将某些内容写入记录器
这有关系吗?也许你不在乎。但是,这个呢
def SubmitOrder(order: Order): Unit =
{
// code that submits an order
}
这不好。稍后见
为什么副作用不好
除了一些明显的原因,包括:
难以理解:必须阅读整个函数体才能看到发生了什么;
可变状态:容易出错,并且可能不是线程安全的
最重要的是,测试很烦人
如何避免副作用
一个简单的方法就是总是尝试归还一些东西。当然,还是尽量不要在内部改变状态,闭包是可以的
例如,上一个示例,如果不是单位,而是:
def SubmitOrder(order: Order): Either[SubmittedOrder, OrderSubmissionError] =
{
// code that submits an order
}
这将是更好的,它告诉读者有一个副作用和可能发生的事情
循环中的副作用
现在回到关于循环的问题,在不分析真实案例的情况下,很难建议如何避免循环的副作用
但是,如果您正在编写一个函数,然后希望编写一个调用该函数的循环,请确保该函数不会修改其他地方的局部变量或状态。所有这些都是很好的答案。如果你来自另一种语言,只需快速添加一点即可 空洞函数 函数不返回任何内容,例如void,这意味着存在副作用 例如,如果您有c语言的代码
void Log (string message) => Logger.WriteLine(message);
这会产生副作用,将某些内容写入记录器
这有关系吗?也许你不在乎。但是,这个呢
def SubmitOrder(order: Order): Unit =
{
// code that submits an order
}
这不好。稍后见
为什么副作用不好
除了一些明显的原因,包括:
难以理解:必须阅读整个函数体才能看到发生了什么;
可变状态:容易出错,并且可能不是线程安全的
最重要的是,测试很烦人
如何避免副作用
一个简单的方法就是总是尝试归还一些东西。当然,还是尽量不要在内部改变状态,闭包是可以的
例如,上一个示例,如果不是单位,而是:
def SubmitOrder(order: Order): Either[SubmittedOrder, OrderSubmissionError] =
{
// code that submits an order
}
这将是更好的,它告诉读者有一个副作用和可能发生的事情
循环中的副作用
现在回到关于循环的问题,在不分析真实案例的情况下,很难建议如何避免循环的副作用
但是,如果您正在编写一个函数,然后希望编写一个调用该函数的循环,请确保该函数不会修改局部变量或其他地方的状态。我想说的是,非终止/发散和部分函数会产生副作用。我想说的是,非终止/发散和部分函数会产生副作用