"Side-effecting lexical closure" vs 函数在 Scala 中



在他的回答评论部分中,apocalisp指出以下内容:

好吧,您确实要求一个功能。a 副作用 [SIC] 词汇闭合 强调不是功能。

他通过"副作用词汇闭合"到底是什么意思,这与功能有何不同?

我的猜测是,他们正在尝试以功能编程意义区分功能 - 在不允许副作用(例如变量状态或输出值状态)的情况下,与少于 procectures 确实有副作用。

如果是这种情况,那么Scala是否会本身就可以区分这种区别,还是仅留给程序员?如果是这样,每个可呼叫(对于缺乏更好的术语)是否没有副作用,并且每个可供电的可调用效果闭合?

闭合只是一个具有"自由变量"的定义,以及为这些自由变量提供绑定的外部环境。在x + 1中,x是一个免费变量;x是什么,因此您只能说出该表达式在包含x的环境中具有什么价值。在y => x + y中,x仍然是免费的,但y不是;该表达式定义了一个函数,并且该函数的参数是y的绑定。

在Scala的背景下,"词汇封闭"基本上只是一个"封闭"(因为它的所有封闭都是词汇),但从技术上讲,"闭合"是一个更一般的概念。"封闭"本身并不是说环境的来源;"词汇封闭"指定它是确定环境的闭合定义的词汇范围(即它在源代码中发生的位置)。

因此,"侧面影响词汇闭合"只是其中之一,它具有副作用。


我对此评论的看法是,apocalisp将功能的数学思想与代码的参数块编程思想进行了对比。不管是他在想的是什么,我都会对此进行扩展:

在数学中,一个函数基本上只是从某些输入集(函数的)中的值映射到某些输出集中的值(其 codomain )。在此视图下,函数不是我们不允许副作用的特殊限制形式,"具有副作用"的概念只是不适用于它。询问数学功能是否具有副作用就像询问颜色黄色是否具有副作用。即使是答案"否"也不是正确的。您所能使用的数学函数要做的就是询问其代码域中的什么值对应于其域中的给定值。如果我具有{ 1 -> 11, 2 -> 22, 3 -> 33 }所描述的函数,并且我问什么CODOMAIN值对应于2,则回答" 22,并且Object Foo的count属性现在是7"。

在理想化的功能编程中,我们将代码视为定义与我们要定义的函数相对应的映射的一种方法。最有趣的功能是无限的(或至少是不切实际的),因此我们不会通过从输入到输出的字面地图上写出字面地图来做到这一点,而是通过写下更多或无用的抽象规则来描述输出如何对应输入。当然,在实践中,我们确实花费大量时间对我们的代码的执行方式进行操作思考,但是通常,功能性的程序员宁愿先考虑定义。

另一方面,传统命令编程中所谓的功能与函数的数学概念无关。程序员的函数不是从域中的值映射到代码域中值的映射,而是一系列步骤,要彼此执行(可能是通过输入值进行参数并返回输出值的参数)。这些步骤中的每个步骤都可能有效果,因此您不能忽略它们的存在,并说这只是定义域 -> Codomain映射的另一种方法,并且不能独立于其上下文来检查它们作为事物。p>在想要支持功能编程和命令编程的编程语言中,您使用相同的语言元素来定义数学函数和程序员的功能。或者,如果您专门使用术语函数来参考数学函数,则使用相同的语言元素来定义两个函数和"其他不是函数的东西"。我以Apolalisp的短语"词汇封闭"为描述了Scala的函数定义 - syntax从功能概念中定义了 exply ,并且当您进一步添加它是"影响词汇闭合的侧面"时,绝对不是这样您正在谈论的功能。

我认为您要寻找的术语是纯且不纯净的函数

具有和没有副作用的功能之间明确不同的功能语言称为 pure 功能语言。一个流行的例子是Haskell。

另一方面,

Scala并非纯净。因此,区别确实留给了程序员。

词汇封闭是与其执行环境相结合的函数。至于"副作用词汇闭合" - 只需读取"副作用函数"。

他只是严格对纯度学科 - a pure 函数必须返回每个值相同的值它被称为时间,并且该问题中的函数f()显然不是。它返回不同的值,因为它突变其封闭变量。

但是,只要其词汇封闭变量的更改是不是直接观察到的任何外部实体,但函数本身都可以直接观察到突变的词汇闭合,只要其词汇封闭变量的更改是直接观察到的 - 此类变量是封装的,隐藏。

Haskell也这样做,无需任何任何单子,这里:

fibgen (a,b) = a : fibgen (b,a+b)
fibs = fibgen (0,1)
f5 = fibs !! 5
another_fibs = fibgen (0,1)

编译器可以通过重复fibgen调用的堆栈框架来可行地将其作为"副作用闭合",因此实际上将其转换为发电机。

说:"绝对不要执行任何突变"就像说"不应该执行goto"。我们只必须谨慎地分离功能和发电机。即使后者看起来像前者,它们也不是。仅此而已。

该问题中代码的真正问题是,它直接创建了像全局对象一样封闭,因此无法重新启动序列。必须控制此类生成封闭的创建 - 只需将初始值纳入参数,然后显式创建新的封闭式:

f1 = make_new_fib_closure(0,1);
f1(), f1(), f1(), ...
....
f2 = make_new_fib_closure(0,1);
....

现在make_new_fib_closure()是一个函数,在每个调用 - fionacci序列上创建相等的值,被视为一个整体。每个f_i都封装了斐波那契序列的一个实例,其状态已生成多远。调用f_i()等于在发电机上调用next()方法。这没什么错。

最新更新