您怎么称呼一个函数,其中相同的输入将始终返回相同的输出,但也会产生副作用?


43

假设我们有一个正常的纯函数,例如

function add(a, b) {
  return a + b
}

然后我们对其进行更改,使其具有副作用

function add(a, b) {
  writeToDatabase(Math.random())
  return a + b;
}

据我所知,它并不是纯函数,因为我经常听到人们称纯函数为“无副作用的函数”。但是,就其对于相同输入将返回相同输出的事实而言,它的行为确实类似于纯函数。

这种函数类型是否有其他名称,是未命名的,还是实际上是纯函数,而我对纯函数的定义有误?


91
“不是纯粹的功能”。
罗斯·帕特森

2
@RossPatterson也是我的想法,但是通过询问我了解了引用透明性,所以很高兴我没有将它保密。
m0meni '16

9
如果writeToDatabase失败,则可能触发异常,从而使您的第二个add函数有时也会产生异常,即使使用与之前没有问题的参数相同的参数调用也是如此...大多数情况下,产生副作用会导致这种与错误相关的情况中断“投入产出纯度”。
巴库里

25
对于给定的输入总能提供相同输出的东西称为确定性
njzk2 '16

2
@ njzk2:是的,但是它也是无状态的。有状态确定性函数可能不会为每个输入都提供相同的输出。示例:F(x)定义为true如果使用与上一次调用相同的参数调用,则返回。显然,该序列{1,2,2} => {undefined, false, true}是确定性的,但为给出不同的输出F(2)
MSalters

Answers:


85

我不确定纯度的通用定义,但是从Haskell(程序员倾向于关心诸如纯度和参照透明性之类的语言)的角度来看,只有第一个函数是“纯净的”。的第二个版本add不是纯粹的。因此,在回答您的问题时,我称其为“不纯净”;)

根据此定义,纯函数是具有以下功能的函数:

  1. 仅取决于其输入。也就是说,给定相同的输入,它将始终返回相同的输出。
  2. 具有参照透明性:可以用函数的值随意替换该函数,并且程序的“行为”不会改变。

使用此定义,很明显您的第二个函数不能被视为纯函数,因为它违反了规则2。也就是说,以下两个程序不等效:

function f(a, b) { 
    return add(a, b) + add(a, b);
}

function g(a, b) {
    c = add(a, b);
    return c + c;
}

这是因为即使两个函数都将返回相同的值,但函数f将两次g写入数据库,但只会写入一次!写入数据库很可能是程序可观察到的行为的一部分,在这种情况下,我已经证明了您的第二个版本add不是“纯”的。

如果对数据库的写操作不是程序行为的可观察部分,则这两个版本add都可以视为等效和纯净。但是我想不出写数据库无关紧要的情况。甚至记录都很重要!


1
给定参照透明性,不是“仅取决于其输入”吗?哪个暗示RT是纯度的代名词?(随着我查找的资源越来越多,我对此感到更加困惑)
Ixrec

我同意这令人困惑。我只能想到人为的例子。说f(x)不仅取决于x,而且还取决于某些外部全局变量y。然后,如果f具有RT的属性,则只要您不触摸, 就可以自由地将所有出现的内容与其返回值交换y。是的,我的榜样令人怀疑。但是重要的是:如果f写入数据库(或写入日志),则会丢失RT的属性:现在,无论是否y保持全局不变都无所谓,您知道程序的含义会根据您实际是否更改而改变。调用f或简单地使用其返回值。
Andres F.

哼。让我们说,我们有一个纯粹的函数,除了副作用,而且在两个样本相等的情况下,也保证具有这种行为。(我实际上已经提出了这个案子,所以这不是假设的。)我认为我们还没有完成。
约书亚

2
我认为第二个功能也可能违反规则1。根据所使用的数据库API的语言和错误处理惯例,如果数据库不可用或数据库由于某种原因失败写入,则该函数可能根本不返回任何内容。
扎克·立顿

1
自从提到Haskell以来:在Haskell中添加这样的副作用需要更改函数的签名。(例如,将原始数据库作为附加输入,将修改后的数据库作为函数的附加返回值)。实际上,可以通过这种方式在类型系统中非常优雅地对副作用建模,这仅仅是当今的主流语言对副作用和纯净度不够关心。
ComicSansMS '16

19

您如何称呼一个函数[对于哪个],相同的输入将始终返回相同的输出,但也会产生副作用?

这样的功能叫做

确定性的

可以从输入中完全预测其行为的算法。

termwiki.com

关于状态:

根据您使用的函数定义,函数没有状态。如果您来自面向对象的世界,请记住这x.f(y)是一种方法。作为功​​能,它看起来像f(x,y)。而且,如果您使用封闭的词法范围进行闭包,请记住,不可变状态也可能是函数表达式的一部分。唯一会影响功能确定性的可变状态。所以f(x)= x + 1是确定性的,只要1不变即可。没关系存储1。

您的功能都是确定性的。您的第一个也是纯函数。你的第二个不纯。

纯功能

  1. 给定相同的参数值,该函数始终求值相同的结果值。函数结果值不能取决于在程序执行过程中或在程序的不同执行之间可能更改的任何隐藏信息或状态,也不能取决于I / O设备的任何外部输入。

  2. 结果评估不会引起任何语义上可观察到的副作用或输出,例如可变对象的突变或输出到I / O设备。

wikipedia.org

点1表示确定性。第2点表示参照透明性。它们在一起意味着纯函数仅允许其参数和其返回值改变。没有别的改变。什么都没改变。


-1。写入数据库取决于外部状态,通常无法通过输入来确定。由于多种原因,数据库可能不可用,并且操作是否成功将不可预测。这不是确定性行为。
Frax

@Frax系统内存可能不可用。CPU可能不可用。确定性并不能保证成功。它保证成功的行为是可预测的。
candied_orange

OOMing不是特定于任何功能的,它是问题的不同类别。现在,让我们看一下“纯函数”定义(确实意味着“确定性”)的第1点:“函数结果值不能依赖于在程序执行过程中或在执行不同的执行之间可能更改的任何隐藏信息或状态。程序,也不取决于I / O设备的任何外部输入。” 数据库是那种状态,因此OP的功能显然不能满足此条件-它不是确定性的。
Frax

@candied_orange我同意是否要写入数据库仅取决于输入。但这是Math.random()。因此,不行,除非我们假设使用PRNG(而不是物理RNG),并且认为PRNG声明了输入的一部分(不是,引用是硬编码的),则它不是确定性的。
marstato

1
@candied_orange对确定性状态的引用“一种可以从输入中完全预测其行为的算法”。对我来说,写IO绝对是一种行为,而不是结果。
marstato '18年

9

如果您不关心副作用,那么它是参照透明的。当然,您可能不在乎,但其他人会在乎,因此该术语的适用性取决于上下文。

我不知道您所描述的属性的通用术语,但是重要的子集是幂等的。在计算机科学中,与数学*稍有不同,幂等函数是可以重复使用而具有相同效果的函数。也就是说,多次执行的净副作用与一次执行相同。

因此,如果您的副作用是用某个行中的某个特定值更新数据库,或者创建内容完全一致的文件,那么它将是幂等的,但是如果将其添加到数据库中或附加到文件中,那么它不会。

幂等函数的组合可能整体上也可能不是幂等。

* 在计算机科学中使用与数学不同的幂等性似乎是由于对数学术语的错误使用,该术语后来被采用,因为该概念很有用。


3
与“是否有人在乎”相比,术语“相对透明”的定义更为严格。即使我们忽略IO的问题,如连接问题,缺少的连接字符串,超时等,那么它仍然是很容易证明的程序替换(f x, f x)let y = f x in (y, y)会遇到磁盘空间不足的例外的快两倍,你可能会说,这些都是您不关心的极端情况,但是由于定义模糊,我们不妨将其称为new Random().Next()参照透明的,因为太过分了,我不管我得到多少。
2016年

@kai:根据具体情况,您可能会忽略副作用。另一方面,像random这样的函数的返回值并不是副作用:它是其主要作用。
乔治,

Random.Next.NET中的@Giorgio 确实有副作用。非常非常。如果可以Next,将其分配给一个变量,然后Next再次调用并将其分配给另一个变量,则它们可能不相等。为什么?因为调用会Next更改Random对象中某些隐藏的内部状态。这与参照透明性相反。我不理解您所说的“主要作用”不能为副作用。在命令式代码中,主要的作用是副作用,这是很普遍的,因为命令式程序本质上是有状态的。
2016年

3

我不知道如何调用此类函数(甚至是否有一些系统名称),但我会调用不纯函数(因为其他答案很畏缩),但如果提供相同的参数,则始终返回相同的结果。参数”(与其参数和某些其他状态的功能相比)。我将其称为函数,但是不幸的是,当我们在编程上下文中说“函数”时,我们指的是根本不必是实际函数的东西。


1
同意!(非正式地)它是“函数”的数学定义,但是不幸的是,就像您说的那样,“函数”在编程语言中的含义有所不同,它更接近于“获取值所需的分步过程”。
Andres F.

2

这主要取决于您是否关心杂质。如果此表的语义是您不关心有多少个条目,那么它就是纯净的。否则,这不是纯粹的。

换句话说,只要基于纯度的优化不会破坏程序语义,就可以了。

一个更现实的示例是,如果您尝试调试此功能并添加了日志记录语句。从技术上讲,日志记录是一个副作用。日志是否不纯?没有。


这要看情况。也许日志使它变得不纯洁,例如,如果您关心多少次,以及什么时候在日志中出现“调用INFO f()”。您经常这样做:)
Andres F.

8
-1日志很重要。在大多数平台上,任何类型的输出都会隐式同步执行线程。程序的行为变得取决于其他线程的写入,外部日志写入器,有时取决于日志读取,文件描述符的状态。它就像一桶土一样纯净。
巴思列夫斯

@AndresF。好吧,您可能不在乎实际次数。您可能只在乎它被调用的次数与调用该函数的次数相同。
DeadMG '16

@Basilevs函数的行为完全不依赖于它们。如果日志写入失败,则继续进行。
DeadMG '16

2
是否选择将记录器定义为执行环境的一部分是一个问题。再举一个例子,如果我将调试器附加到进程并在其上设置断点,我的纯函数是否仍然是纯函数?从使用调试器的人的POV来看,该功能显然具有副作用,但是通常我们以“不计算在内”的惯例来分析程序。用于调试的日志记录可以(但不必)去同样的地方,我想这就是为什么trace隐藏了其杂质。关键任务日志记录(例如用于审核)当然一个严重的副作用。
Steve Jessop

1

我想说的最好的问题不是我们怎么称呼它,而是我们如何分析这样的一段代码。在这种分析中,我的第一个关键问题是:

  • 副作用是取决于函数的参数还是副作用的结果?
    • 否: “有效功能”可以重构为纯功能,有效动作和组合它们的机制。
    • 是: “有效函数”是产生单子结果的函数。

这很容易在Haskell中进行说明(而这句话只是一个笑话而已)。例如,“否”的情况如下所示:

double :: Num a => a -> IO a
double x = do
  putStrLn "I'm doubling some number"
  return (x*2)

在此示例中,我们采取的操作(打印行"I'm doubling some number"x对结果之间的关系没有影响。这意味着我们可以通过这种方式(使用Applicative类及其*>运算符)对其进行重构,这表明函数和效果实际上是正交的:

double :: Num a => a -> IO a
double x = action *> pure (function x)
  where
    -- The pure function 
    function x = x*2  
    -- The side effect
    action = putStrLn "I'm doubling some number"

因此,在这种情况下,我个人会说,这是您可以分解出纯函数的情况。许多Haskell编程都与此有关-学习如何从有效代码中剔除纯净的部分。

“是”排序的示例,其中纯净部分和有效部分不正交:

double :: Num a => a -> IO a
double x = do
  putStrLn ("I'm doubling the number " ++ show x)
  return (x*2)

现在,您打印的字符串取决于的值x。但是,函数部分(乘以x2)完全不依赖于效果,因此我们仍然可以将其分解出来:

logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
  logger a
  return (function a)

double x = logged function logger
  where function = (*2) 
        logger x putStrLn ("I'm doubling the number " ++ show x)

我可以继续阐明其他示例,但是我希望这足以说明我的出发点:您不要“称呼”它,您要分析纯净的部分和有效的部分之间的联系,并在它们存在时予以分解。对你有利。

这是Haskell Monad如此广泛使用其类的原因之一。Monads(除其他外)是执行这种分析和重构的工具。


-2

旨在引起副作用的功能通常称为有效的。示例https://slpopejoy.github.io/posts/Effectful01.html


仅回答提及广为人知的有效术语,它就被否决了。……我想,无知是幸福。..
Ben Hutchison's

“有效”一词是该职位的作者选择的意思是“具有副作用”。他自己也这么说。
罗伯特·哈维

谷歌搜索有效功能揭示了大量证据,这是一个广泛使用的术语。博客文章是作为许多示例之一给出的,而不是作为定义给出的。在纯函数为默认函数的函数式编程圈子中,需要一个积极的术语来刻意描述副作用函数。即不仅仅是缺乏纯洁。这个词很有效。现在,考虑一下自己受过教育。
Ben Hutchison

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.