我还不清楚副作用的概念。
- 编程的副作用是什么?
- 是否依赖于编程语言?
- 是否存在外部和内部副作用?
请举例说明产生副作用的原因。
我还不清楚副作用的概念。
请举例说明产生副作用的原因。
Answers:
一个副作用仅仅指的是某种状态的改变-例如:
与某些人似乎说的相反:
副作用并没有要隐藏的或意外的(也可以是,但有没有关系,因为它适用于计算机科学的定义);
副作用与幂等性无关。幂等函数可能有副作用,而非幂等函数可能没有副作用(例如获取当前系统日期和时间)。
这真的非常简单。副作用=在某处更改某些内容。
PS正如评论者benjol指出的那样,可能会有几个人将副作用的定义与纯函数的定义混淆,纯函数是(a)幂等且(b)没有副作用的函数。在一般计算机科学中,一种并不暗示另一种,但是功能编程语言通常倾向于强制执行这两种约束。
++a
。看起来不像作业。 b = ++a;
有两个副作用。的明显的一个和的密码分配a
。这是(某些人)希望得到的副作用。但是一直被认为是我整个职业的副作用。
修改计算机状态或与外界交互的任何操作都被认为具有副作用。参见有关副作用的维基百科。
例如,此功能没有副作用。其结果仅取决于其输入参数,而在调用该程序时,与程序状态或环境无关的内容不会改变:
int square(int x) { return x * x; }
相反,根据调用它们的顺序,调用这些函数将给您不同的结果,因为它们会改变计算机的状态:
int n = 0;
int next_n() { return n++; }
void set_n(int newN) { n = newN; }
此功能具有将数据写入输出的副作用。您不调用该函数是因为您需要它的返回值。之所以称呼它是因为您希望它对“外部世界”具有影响:
int Write(const char* s) { return printf("Output: %s\n", s); }
Write
示例所示,具有副作用并不意味着该函数会相对于其输入更改其输出,甚至并不表示该函数的输出完全取决于输入。
square(x)
可能导致定义功能的模块从磁盘加载。应该认为这是副作用吗?毕竟,这男子的(第一)调用花费意外长,即RAM的使用上升等
square(x)
由于要更改外部计算机状态而进行呼叫,则可以认为这是副作用。
我认为现有的答案是相当不错的。我想在某些方面阐述海事组织的压力不够大。
在数学中,函数只是从值的元组到值的映射。因此,给定一个函数f
和一个值x
,f(x)
将始终是相同的结果y
。你可能会取代f(x)
与y
无处不在的表达,不会有任何变化。
在许多编程语言中,所谓的函数(或过程)是可以执行的构造(代码段),因为:
因此效果可能与状态有关,也可能与其他方面有关,例如发射导弹或暂停执行几秒钟。
副作用一词听起来可能是负面的,但通常调用函数的作用是函数本身的目的。我猜想,由于术语“函数”最初是在数学中使用的,因此计算值被认为是函数的主要作用,而其他任何作用都被认为是副作用。一些编程语言使用术语过程来避免与数学意义上的函数混淆。
注意
sleep()
,Python中的过程)仅对其(副作用)有用。这些通常被建模为返回特殊值None
或unit
或()
或...的函数,它们仅表示计算已正确终止。副作用是操作对超出预期用途的变量/对象产生影响时。
当您调用具有更改某些全局变量的副作用的复杂函数时,可能会发生这种情况,即使这不是您调用它的原因(也许您调用它是为了从数据库中提取某些内容)。
我承认我很难提出一个看起来并不完全人为的简单示例,而且我所研究的内容中的示例太久了,无法在此处发布(并且由于与工作相关,因此我可能无论如何都不应该这样做) )。
我之前看到的一个示例是一个函数,如果连接处于关闭状态,则该函数将打开数据库连接。问题在于应该在函数末尾关闭连接,但是开发人员忘记添加该代码。因此,这里有一个意外的副作用:调用过程只应该执行查询,并且副作用是连接保持打开状态,如果连续两次调用该函数,则会引发错误,指出连接为已经打开。
好的,既然大家现在都在举一些例子,我想我也会;)
/*code is PL/SQL-styled pseudo-code because that's what's on my mind right now*/
g_some_global int := 0; --define a globally accessible variable somewhere.
function do_task_x(in_a in number) is
begin
b := calculate_magic(in_a);
if b mod 2 == 0 then
g_some_global := g_some_global + b;
end if;
return (b * 2.3);
end;
该函数do_task_x
具有一个主返回一些计算的结果的影响,和一个侧面的可能修改的全局变量的效果。
当然,这是主要的,也是副作用,可能需要解释,并且可能取决于实际用法。如果我出于修改全局变量的目的调用此函数,并且放弃返回的值,那么我会说修改全局变量是主要的作用。
在计算机科学中,如果函数或表达式修改某些状态或与调用函数或外界有可观察的交互作用,则称其具有副作用。
来自维基百科-副作用
从数学意义上讲,函数是从输入到输出的映射。调用函数的预期效果是使函数将输入映射到返回的输出。如果该函数执行其他任何操作,则无关紧要,但是,如果该函数具有未将输入映射到输出的任何行为,则该行为被认为是副作用。
更一般地讲,副作用是不是构造设计者的预期效果的任何效果。
效果是影响演员的任何事物。如果我调用一个向女友发送分手短信的功能,则该功能会影响一堆演员,包括我,她,手机公司的网络等。调用该功能的唯一目的是该功能从我的输入返回一个映射。因此对于:
public void SendBreakupTextMessage() {
Messaging.send("I'm breaking up with you!")
}
如果打算将其用作函数,则它唯一要做的就是返回void。如果没有副作用,则它实际上不应发送短信。
在大多数编程语言中,没有数学函数的构造。没有打算照此使用任何构造。这就是为什么大多数语言都说您有方法或过程的原因。通过设计,它们旨在能够产生更多的效果。用普通的编程术语来说,没有人真正关心方法或过程的意图,因此当有人说此函数有副作用时,他们实际上是在说,这种构造的行为不像数学函数。当有人说这个函数没有副作用时,他们的意思是说,这个结构实际上就像数学函数一样工作。
根据定义,纯函数始终无副作用。纯粹的函数可以说是一种函数,即使它使用的结构允许更多的效果,但实际上仅具有与数学函数相同的效果。
我挑战任何人告诉我什么时候无副作用的功能将不纯净。除非使用术语“纯”和“无副作用”的句子上下文的主要预期效果不是函数的数学预期效果,否则它们始终相等。
因此,有时(尽管很少),并且我认为这是公认的答案中缺乏区别并且误导人们的原因(因为这不是最常见的假设),但是有时假定编程功能的预期效果是将输入映射到输出,其中输入不限于函数的显式参数,而输出则限于显式返回值。如果您认为这是预期效果,那么由于允许输入来自预期效果中的其他位置,因此读取文件并基于文件中内容返回不同结果的函数仍然没有副作用。
那么,为什么这一切都很重要?
这一切都与控制和保持控制有关。如果调用一个函数,然后又执行其他操作然后返回一个值,则很难推断出其行为。您将需要在函数内部查找实际代码,以猜测其作用并断言其正确性。理想的情况是,很清楚并且很容易知道函数正在使用什么输入,并且它没有做任何其他事情,然后为它返回输出。您可以对此稍作放松,并说确切地知道它正在使用什么输入并不像确定它没有做任何您可能不知道的然后返回值的事情那样有用,所以也许您对仅执行感到满意它不执行任何其他操作,然后将输入(无论从何处获取)映射到输出。
在几乎所有情况下,程序的目的都是要产生影响,然后再将输入的内容映射到输出的内容上。控制副作用的想法是,您可以以一种易于理解和推理的方式来组织代码。如果将所有副作用汇总在一起,放在一个非常明确和重要的地方,就很容易知道在哪里看,并相信这就是正在发生的一切,仅此而已。如果您的输入也非常明确,则可以帮助测试不同输入的行为,并且使用起来更容易,因为您无需在很多不同的地方更改输入,只是其中一些可能并不明显。得到你想要的。
因为最有助于理解,推理和控制程序行为的地方是将所有输入清楚地分组在一起并且是明确的,并且将所有副作用都分组在一起并且是明确的,这通常是人们在谈论时所说的副作用,纯等
因为最有用的是对副作用及其明确性进行分组,所以有时人们只会表示此意思,并通过说它不是纯净的,而是仍然是“副作用”自由的来区分它。但是副作用是相对于假定的“预期的主要作用”的,因此它是一个上下文术语。我发现这种用法很少使用,尽管令人惊讶的是在该线程中谈论了很多。
最后,幂等意味着用相同的输入多次调用此函数(无论它们来自何处)始终会产生相同的效果(是否有副作用)。
这是一个简单的示例:
int _totalWrites;
void Write(string message)
{
// Invoking this function has the side effect of
// incrementing the value of _totalWrites.
_totalWrites++;
Debug.Write(message);
}
副作用的定义并非特定于编程,因此只需想象一下药物或进食过多的副作用即可。
x++
修改变量的事实x
通常被认为是副作用。表达式的值是的预增值x
;这是表达式的非副作用部分。
副作用是代码中发生的事情并不明显。
例如,假设您有这个课程
public class ContrivedRandomGenerator {
public int Seed { get; set; }
public int GetRandomValue()
{
Random(Seed);
Seed++;
}
}
最初创建类时,您要为其提供种子。
var randomGenerator = new ContrivedRandomGenerator();
randomGenerator.Seed = 15;
randomGenerator.GetRandomValue();
您不知道内部结构,只是希望获得一个随机值,并且您希望randomGenerator.Seed仍为15 ...但事实并非如此。
该函数调用具有更改种子值的副作用。