编程中的副作用是什么?
它是否依赖于编程语言?
是否有这样的事情?作为外部和内部副作用?
请举例说明产生副作用的原因。
#1 楼
副作用仅指对某种状态的修改,例如:更改变量的值;
将某些数据写入磁盘;
在用户界面中启用或禁用按钮。
与某些人似乎在说的相反:
副作用不必隐藏或隐藏意料之外的(可以,但是与计算机科学中的定义无关);
副作用与幂等性无关。幂等函数可能有副作用,而非幂等函数可能没有副作用(例如获取当前系统日期和时间)。
这非常简单。副作用=在某处更改某些东西。
P.S.正如评论者benjol指出的那样,可能有好几个人将副作用的定义与纯函数的定义混为一谈,纯函数的作用是(a)幂等,而(b)没有副作用。在一般的计算机科学中,一种并不暗示另一种,但是功能编程语言通常倾向于强制执行这两种约束。
评论
短语“副作用”听起来好像正在改变其他东西,而不是原本打算的。在医学上,药物的主要作用是减轻疼痛,有时还会引起流鼻血,头昏眼花等副作用。药物的目的不是引起流鼻血,而是有时以意外的额外结果。
– FrustratedWithFormsDesigner
11年1月26日在22:18
@沮丧:+1。每当我看到该术语时,我都会忍不住想问,FP倡导者是否没有选择它来精确地创建这种微妙的险恶含义。
–梅森·惠勒
2011年1月26日23:07
@梅森·惠勒它早在FP之前就已存在。这不是一个险恶的含义。这完全是邪恶的,而且一直存在。在我编写代码的30年中,“加密分配”声明(即副作用)一直困扰着人们。一个简单的旧赋值语句要容易得多。
– S.Lott
2011-1-27的0:22
@梅森·惠勒:在C。++ a中。看起来不像作业。 b = ++ a;有两个副作用。很明显的一个和a的密码分配。这是(某些人)希望得到的副作用。但是一直被认为是我整个职业的副作用。
– S.Lott
2011年1月27日,下午4:13
@Zachary,请参阅我的答案的最后一个要点。您指的是幂等行为(或缺乏幂等行为)。那没有告诉你任何有关副作用的信息。检查系统时钟不是副作用。实际上,任何以“ get”为前缀的函数或方法都是您应该合理预期不会产生任何副作用的函数或方法。
– Aaronaught
2011-2-15在16:03
#2 楼
修改计算机状态或与外界交互的任何操作都被认为具有副作用。有关副作用,请参见Wikipedia。例如,此功能没有副作用。其结果仅取决于其输入参数,而在调用该程序时,程序状态或环境不会改变: int square(int x) { return x * x; }
此功能具有将数据写入输出的副作用。您不调用该函数是因为您需要它的返回值。之所以调用它,是因为您希望它对“外部世界”具有影响:
int n = 0;
int next_n() { return n++; }
void set_n(int newN) { n = newN; }
评论
这是一个很好的定义,但是我对它的详尽描述并不感到疯狂-正如索比约恩的回答一样,它的一部分似乎将副作用与幂等函数混为一谈。正如您的Write示例所演示的那样,具有副作用并不表示该函数曾经相对于其输入更改其输出,甚至不表示该函数的输出完全取决于输入。
– Aaronaught
2011年1月26日在20:14
这与成为幂等无关。它产生输出的事实意味着它具有副作用。
–克里斯托弗·约翰逊(Kristopher Johnson)
2011年1月26日23:42
在某些系统中,调用square(x)可能会导致从磁盘加载定义了功能的模块。应该认为这是副作用吗?毕竟,这使(第一个)调用耗时异常长,RAM使用率上升等等。
–哈根·冯·埃岑
17年6月20日在16:31
@HagenvonEitzen实际上,每个操作都会更改计算机的状态(CPU寄存器,内存,功耗,热量等)。 “副作用”通常是指虚构的理想执行环境,除非程序明确对其进行更改,否则该环境不会改变。但是,如果由于要更改外部计算机状态而调用square(x),则可以认为这是副作用。
–克里斯托弗·约翰逊(Kristopher Johnson)
17-10-6在15:43
对我来说,第一个插图是很合理的。但是,第二个则不然。我认为应该根据特定的环境/范围来定义副作用。如果您考虑整个宇宙,那是没有副作用的。即使将其限制在计算机上,您的功能也会影响其他进程,因为CPU的行为将不同。如果将作用域限制为局部函数作用域中可访问的事物,那么我们有话要说。
–funct7
19-10-14在22:43
#3 楼
我认为现有的答案是相当不错的。我想在某些方面详细说明IMO的压力不够。在数学中,函数只是从值的元组到值的映射。因此,给定一个函数
f
和一个值x
,f(x)
将始终是相同的结果y
。您可以在表达式中的任何地方用f(x)
替换y
,并且什么都不会改变。在许多编程语言中所谓的函数(或过程)是可以执行的构造(代码段),因为:
它在数学意义上计算一个函数,即给定输入值,它返回结果,或者
它产生某种效果,例如在屏幕上打印内容,更改数据库中的值,发射导弹,睡眠10秒钟,发送SMS。
因此效果不仅与状态有关,而且还与发射导弹或暂停执行几秒钟。
副作用一词听起来可能是负面的,但是通常调用函数的作用是函数本身的目的。我猜想,由于函数一词最初是在数学中使用的,因此计算值被认为是函数的主要作用,而其他任何作用都被认为是副作用。
一些编程语言使用术语过程来避免
请注意,
某些过程对于返回值和副作用都是有用的。
某些过程仅计算结果值,而没有其他影响。它们之所以称为纯函数,是因为它们所做的只是在数学意义上计算一个函数。
一些程序,例如Python中的
sleep()
仅对它们的(副作用)有用。这些通常被建模为返回特殊值None
或unit
或()
或...的函数,它们仅表示计算已正确终止。评论
以我的拙见,这应该是公认的答案。副作用的概念甚至仅在数学函数方面才有意义。该过程旨在简单地以结构化的方式对一组指令进行分组,同时使您可以从任何地方跳转到该指令集并方便地返回。没有主要的预期效果,也没有副作用。您可能会说抛出异常是过程的副作用,因为它破坏了过程的意图,即使您跳回上次停止的地方并在那里继续执行。
– DiidierA。
16年7月22日在17:04
这是我在互联网上遇到的最好的答案。干杯!
– Govinda Malavipathirana
19/12/20在18:48
#4 楼
副作用是指操作对超出预期用途的变量/对象产生影响的情况。调用具有以下副作用的复杂函数时,可能会发生这种情况:更改某些全局变量,即使这不是您调用它的原因(也许您调用它是为了从数据库中提取某些内容)。
我承认我在想出一个简单的示例时遇到了麻烦看起来并不完全是人为的,而且我所研究的内容中的示例太过冗长,无法在此处发布(由于与工作相关,因此我还是不应该这样做)。
我举了一个示例我们已经看到(前一阵子),如果连接处于关闭状态,它将打开数据库连接。问题在于应该在函数末尾关闭连接,但是开发人员忘记添加该代码。因此,这里有一个意外的副作用:调用过程只应该执行一个查询,并且副作用是连接保持打开状态,如果连续两次调用该函数,则会引发错误,指出连接为已经打开。
好,所以既然现在每个人都在举一些例子,我想我也可以;)
/*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
具有返回某些计算结果的主要作用,并且具有可能修改全局变量的副作用。当然,这是主要作用,而副作用是可以接受解释的并可能取决于实际使用情况。如果我出于修改全局变量的目的调用此函数,并且放弃返回值,则不会说修改全局变量是主要作用。
评论
我认为这不是一个很好的通用定义。许多程序员特意使用其副作用的构造。
– CB Bailey
2011年1月26日19:57
@查尔斯:足够公平。在这种情况下,您将如何定义它?
– FrustratedWithFormsDesigner
2011年1月26日19:59
我认为@KristopherJohnson具有最清晰的定义。改变程序状态或其环境或产生现实效果的任何事物,例如生成输出。
– CB Bailey
11年1月26日在20:02
@查尔斯·贝利:这不会改变定义。用东西做副作用很好。只要您了解其中就有副作用。它不会改变此定义的任何内容。
– S.Lott
2011-1-27的0:24
@SLott:此答案中的定义(即第一段)包括以下子句:“超出预期用途”。我认为我的评论是公正的。
– CB Bailey
2011年1月27日7:36
#5 楼
在编程中,副作用是过程从其范围之外更改变量时。副作用与语言无关。有一些旨在消除副作用的语言(纯函数式语言),但我不确定是否有任何需要副作用的语言,但我可能是错的。我知道,没有内部和外部的副作用。
评论
更准确地说,纯函数式语言将无副作用的代码与其他代码清楚地分开了,而其他语言则没有区分纯代码和不纯代码的机制。大多数程序都必须具有副作用才能使用。
–乔治
2014年11月6日在22:03
我认为某些pre-gui编程语言,例如MS-BASIC和QBasic,可能已经尽可能接近“仅副作用”语言。是的,您可能同时具有内部和外部副作用。
–詹姆斯K
17-6-27在3:17
#6 楼
在计算机科学中,如果函数或表达式修改了某些状态或与调用函数或外界具有可观察的交互作用,则称该函数或表达式具有副作用。
从Wikipedia-副作用
从数学上讲,函数是从输入到输出的映射。调用函数的预期效果是使函数将输入映射到返回的输出。如果该函数执行其他任何操作,则无关紧要,但是,如果该函数具有未将输入映射到输出的任何行为,则该行为被认为是副作用。
在更多内容中一般而言,副作用是指不是构造设计者预期效果的任何效应。
效应是指影响演员的任何事物。如果我调用一个向女友发送分手短信的功能,则该功能会影响一堆演员,包括我,她,手机公司的网络等。调用无副作用功能的唯一预期效果是该功能从我的输入返回一个映射。因此适用于:
public void SendBreakupTextMessage() {
Messaging.send("I'm breaking up with you!")
}
如果要用作函数,则唯一要做的就是返回void。如果没有副作用,则实际上不应该发送文本消息。
在大多数编程语言中,没有数学函数的构造。没有构造意欲照此使用。这就是为什么大多数语言都说您有方法或过程的原因。通过设计,它们旨在能够产生更多的效果。用一般的编程术语来说,没有人真正关心方法或过程的意图,因此当有人说此函数有副作用时,他们实际上是在说,这种构造的行为不像数学函数。当有人说这个函数没有副作用时,他们的意思是说,这种构造实际上就像数学函数一样。
根据定义,纯函数始终无副作用。纯粹的函数可以说是一种函数,即使它使用的结构允许更多的效果,但实际上仅具有与数学函数相同的效果。
我向任何人挑战告诉我什么时候没有副作用的函数不是纯净的。除非使用术语“纯”和“无副作用”的句子上下文的主要预期效果不是函数的数学预期效果,否则它们始终相等。
因此,有时,尽管这种情况很少见,但我认为这是公认的答案中缺乏区别的地方,而且也误导了人们(因为这不是最常见的假设),但有时会假定编程功能的预期效果是将输入映射到输出,其中输入不限于函数的显式参数,而输出则限于显式返回值。如果您认为这是预期效果,那么由于允许输入来自预期效果中的其他位置,因此读取文件并基于文件中内容返回不同结果的函数仍然没有副作用。 />
那么,为什么这一切都很重要?
这一切都与控制和保持控制有关。如果调用一个函数,然后又执行其他操作然后返回一个值,则很难推断出它的行为。您将需要在函数内部查找实际代码,以猜测其作用并断言其正确性。理想的情况是,很清楚并且很容易知道函数正在使用什么输入,并且它没有做其他任何事情然后为它返回输出。您可以对此稍作放松,并说确切地知道它正在使用什么输入并不像确定它没有做任何您可能不知道的然后返回值的事情那样有用,所以也许您对仅执行感到满意它不执行任何其他操作,然后将输入(无论它从何处获取)映射到输出。
在几乎所有情况下,程序的作用是产生影响,然后再映射正在运行的内容对即将出现的事情。控制副作用的想法是,您可以以一种易于理解和推理的方式来组织代码。如果将所有副作用汇总在一起,放在一个非常明确和关键的地方,就很容易知道在哪里看,并相信这就是正在发生的一切,仅此而已。如果您的输入也非常明确,则可以帮助测试不同输入的行为,并且使用起来更容易,因为您无需在很多不同的地方更改输入,只是其中一些可能并不明显。获得所需的内容。
因为最有助于理解,推理和控制程序的行为的方法是,将所有输入清楚地分组在一起,并进行显式分组,并将所有副作用分组在一起。在一起并且很明确,这就是人们通常所说的副作用,纯净的意思。
因为最有用的是对副作用及其明确性进行分组,所以有时人们只会表示此意思,并通过说它不是纯净的,而是仍然具有“副作用”自由来区分它。但是副作用是相对于假定的“预期的主要作用”的,因此它是一个上下文术语。我发现它很少使用,尽管令人惊讶的是在该线程中谈论了很多。
最后,幂等意味着用相同的输入多次调用此函数(无关紧要,它们来自何处)总是会产生相同的效果(是否有副作用)。
评论
我认为解释副作用的一个大问题是,除非您使用过像Ocaml或Haskell这样的语言,否则很难推理出无副作用的(几乎!)编程。
–Jamie Strauss
18年4月4日在16:06
#7 楼
这是一个简单的示例:int _totalWrites;
void Write(string message)
{
// Invoking this function has the side effect of
// incrementing the value of _totalWrites.
_totalWrites++;
Debug.Write(message);
}
副作用的定义并非特定于编程,因此只需想象一下药物或进食过多的副作用即可。 />
评论
但是,如果将消息作为参考,而您在方法中更改消息,则可能会产生副作用。我对么?
–阿米尔·雷扎伊(Amir Rezaei)
2011年1月26日19:59
表达式x ++修改变量x的事实通常被认为是副作用。表达式的值是x的预增值;这是表达式的非副作用部分。
– CB Bailey
2011年1月26日19:59
@Charles-我同意,尽管原始示例并不像当前示例那么清晰。
–混沌潘迪翁
2011年1月26日在20:01
@Amir-嗯,这真的取决于语言。如果这是C#,则不会被视为副作用。
–混沌潘迪翁
2011-1-26在20:03
@ChaosPandion:我个人不同意。原始示例更加简单明了。
– CB Bailey
2011-1-26在20:03
#8 楼
副作用是代码中发生的事情并不太明显。例如,假设您拥有此类
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 ...但是不是。
函数调用具有更改Seed值的副作用。
评论
副作用不必隐藏。您正在考虑口语或医疗用途;在编程中,副作用只是指修改某些状态。
– Aaronaught
2011-1-26在20:03
打印到控制台是一个副作用。它不是隐藏的。来自维基百科:“在计算机科学中,如果函数或表达式除了返回值之外还修改某些状态或与调用函数或外界具有可观察的交互作用,则该函数或表达式具有副作用。”
–user40980
2015年7月3日在13:53
副作用是非函数(即过程)如何完成任何工作。 X = 1; X = Y(10)是两个纯函数。当您走出“ x = what”境界时,是将输出写入屏幕还是在“ x = y”格式之外读取输入,还是仅将变量的值从一件事更改为另一件事? ,这是一个副作用。
–詹姆斯K
17年6月27日在2:23
我认为“隐藏”是指不明显。像在x = f(y,z)中一样,可以假定x基于y和z。而proc(x,y,z)告诉您发生了什么。每个变量都可以更改,也可以不更改。 Proc可以是f的类似物,或完全不相关。一个纯函数有一个答案:“ x”。除此之外,这是副作用。完全意图,但有副作用。
–詹姆斯K
17年6月27日在2:37
就像了解0一样,您必须首先了解1:要了解副作用,必须首先了解功能。
–詹姆斯K
17年6月27日在2:41
评论
听起来很像功课。关心这个的@ gnasher729非常有用:)