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

我目前正在学习使用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 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 
}
这将是更好的,它告诉读者有一个副作用和可能发生的事情

循环中的副作用

现在回到关于循环的问题,在不分析真实案例的情况下,很难建议如何避免循环的副作用


但是,如果您正在编写一个函数,然后希望编写一个调用该函数的循环,请确保该函数不会修改局部变量或其他地方的状态。

我想说的是,非终止/发散和部分函数会产生副作用。我想说的是,非终止/发散和部分函数会产生副作用