为什么println被认为是不纯函数?


10

我正在阅读Scala中的编程书籍,据说:

...在这种情况下,其副作用是打印到标准输出流。

而且我看不到副作用在哪里,因为对于相同的输入,println会在每次 调用时输出相同的输出(我认为)
UPDATE
,例如:

println(5)

它会打印5,我看不到调用println(5)将打印5以外的其他值的情况!


如果这回答了您的问题,我将删除我的答案softwareengineering.stackexchange.com/q/40297/271736
joelb

3
关于什么是参照透明性的答案在这里似乎很相关。
内森·休斯


2
您将确定性混淆了副作用(不是透明的参照)。println是确定性函数,但是为了纯净它也必须是RT。
鲍勃

2
因为除了计算结果并返回结果外,它还执行其他操作。
塞斯·提苏

Answers:


6

您可以通过将表达式替换为其结果来判断表达式是否有副作用。如果程序改变了含义,那会有副作用。例如,

println(5)

是一个不同的程序

()

也就是说,副作用是在评估表达式的结果中未编码的任何可观察到的效果。结果是(),但是该值中没有任何东西可以编码5现在已经出现在屏幕上的事实。


6
实际上,这不是“副作用”的好定义-副作用可以定义为破坏参照透明性的任何东西。您试图在此处显示的是RT,但您的示例是错误的。由于多次执行某项操作应该多次执行相同的操作-而是val a = println("hello"); val b = (a, a)应与相同val b = (pritnln("hello"), println("hello"))
Luis MiguelMejíaSuárez

1
@LuisMiguelMejíaSuárez也许我的例子不清楚,但我认为这是不对的。我本质上是在指出程序println(5)与的区别()。还是您的意思是最后一句话?
joelb

是的,但您不清楚。就像我说的那样,由于问题不是多次调用,因此问题在于用其定义替换引用会产生这种影响。
Luis MiguelMejíaSuárez

我并不清楚地了解你的榜样
aName

5
我会说这是错误的,因为某些东西完全有可能产生副作用并且是幂等的,因此重复进行不会改变效果。例如,给可变变量赋值;您如何区分x = 1x = 1; x = 1; x = 1
Alexey Romanov

5

考虑以下类比

var out: String = ""
def myprintln(s: String) = {
  out += s // this non-local mutation makes me impure
  ()
}

myprintln是不纯净的,因为除了返回值之外,()它还会将非局部变量out作为副作用进行突变。现在想象一下out是流香草println突变。


1
谢谢您的回应,很明显您的函数是不纯正的,但是,为什么在scala中定义的println()不纯
aName

1
@aName因为除了返回值之外(),它还突变了中的非本地状态System.out
Mario Galic

我认为此答案缺少的关键事实是println在输入中添加了换行符。
Federico S

4

副作用是计算机的状态。每次调用时println(),内存状态都会更改,以便向终端显示给定值。或更一般地,标准输出流的状态被改变。


1
部分正确,执行任何操作都会使指令计数器处于状态,因此任何事情都是副作用。副作用的定义源自参照透明性的定义,该参考透明性是许多人根据对共享可变状态的修改来定义的。
Luis MiguelMejíaSuárez

2
这样,任何功能,操作...都将是不纯正的,因为它会更改,从而导致CPU的状态....
aName

2

对于这个问题已经给出了很好的答案,但是让我加上两美分。

如果您要查看内部println函数的本质,则与java.lang.System.out.println()- 相同,因此,当您在内部调用Scala的标准库println方法时,它将printlnPrintStream对象实例调用方法,该对象实例outSystem类(或更确切地说outVarConsole对象中)中声明为字段,从而更改其内部状态。这可以看作是另一个解释为什么println功能不纯的原因。

希望这可以帮助!


1

它与参照透明性的概念有关。如果您可以在不更改程序的情况下将表达式替换为其评估结果,则该表达式是参照透明

当表达式不是参照透明的时,我们说它有副作用

f(println("effect"), println("effect"))
// isn't really equivalent to!
val x = println("effect")
f(x, x)

import cats.effect.IO

def printlnIO(line: String): IO[Unit] = IO(println(line))

f(printlnIO("effect"), printlnIO("effect"))
// is equivalent to
val x = printlnIO("effect")
f(x, x)

您可以在此处找到更详细的说明:https : //typelevel.org/blog/2017/05/02/io-monad-for-cats.html


我不明白为什么f(x,x)与f(println(“ effect”),println(“ effect”)))不同!
aName

f(println("effect"), println("effect"))在控制台“效果”中要打印两次,而val x = println("effect");f(x,x)要打印一次。
Didac Montero

函数f的定义是什么
aName
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.