Scala中的“副作用词汇闭包”与功能

时间:2014-03-09 21:21:28

标签: function scala functional-programming closures side-effects

his answer's comment section中,Apocalisp声明如下:

  

嗯,你确实要求一个功能。 侧面有效 [sic] 词汇封闭   显然不是一种功能。

“副作用词汇闭包”究竟是什么意思,它与功能有何不同?

我的猜测是,他们试图在功能编程意义上区分功能 - 不允许任何副作用(例如改变变量状态或输出值),仅仅来自程序,确实有副作用。

如果是这种情况,那么Scala会自己做出这种区分,还是只留给程序员?如果是这样,那么每个可调用的(没有更好的术语)是没有副作用的函数,还是每个可调用的副作用都会产生词汇封闭?

3 个答案:

答案 0 :(得分:4)

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

Scala上下文中的“词法闭包”基本上只是一个“闭包”(因为它的所有闭包都是词法)但技术上“闭包”是一个更普遍的概念。 “关闭”本身并没有说明环境来自何处; “词法闭包”指出闭包定义的词法范围(即它在源代码中出现的位置)决定了环境。

所以“副作用词汇封闭”只是其中之一,有副作用。


我对该评论的看法是,Apocalisp将函数的数学概念与要执行的参数化代码块的编程思想进行了对比。不管这是的想法,我会扩展我的想法:

在数学中,函数基本上只是从某些输入集(函数的)中的值到某些输出集中的值(其 codomain )的映射。在这种观点下,功能不是一种特殊的限制形式的程序,我们不允许副作用,“有副作用”的概念就不适用于它。询问数学函数是否有副作用就像询问黄色是否有副作用一样;即使答案“不”也不是真的正确。你可以用数学函数做的就是询问它的codomain中的哪个值对应于其域中的给定值;如果我有一个由{ 1 -> 11, 2 -> 22, 3 -> 33 }描述的函数,并且我问什么codomain值对应于2,则回答“22,而对象foo的count属性现在为7”是没有意义的。

在理想化的函数式编程中,我们将代码视为一种定义与我们想要定义的函数相对应的映射的方法。最有趣的函数是无限的(或者至少是不切实际的巨大的),所以我们不是通过写出从输入到输出的文字映射而是通过写下描述输出如何与输入相对应的或多或少的抽象规则来实现的。当然,在实践中,我们花了很多时间在操作上思考如何执行代码,但通常函数式程序员宁愿首先考虑定义。

另一方面,传统命令式编程中所谓的函数与函数的数学概念几乎没有关系;程序员的函数是一系列要一个接一个地执行的步骤(可能由输入值参数化并返回输出值),而不是从域中的值到codomain中的值的映射。这些步骤中的每一步都可能产生影响,因此您不能忽视它们的存在并说它只是定义域的另一种方式 - > codomain mapping,并且不能将它们作为独立于其上下文的事物进行检查。

在一种想要支持函数式编程和命令式编程的编程语言中,您使用相同的语言元素来定义数学函数和程序员的函数。或者,如果您专门使用术语函数来引用数学函数,则使用相同的语言元素来定义函数和“其他不是函数的函数”。我把Apolalisp的短语“lexical closure”描述为描述Scala的函数定义语法将分开定义为函数的概念,当你进一步补充说它是一个“副作用词汇闭包”时,它绝对不是你正在谈论的一个功能。

答案 1 :(得分:1)

我认为您要查找的字词为pure and impure function

在具有和不具有副作用的函数之间明确区分的函数语言称为函数语言。一个流行的例子是Haskell。

另一方面,斯卡拉并不纯洁。所以这种区别确实留给了程序员。

lexical closure是一个与其执行环境相结合的功能。至于“副作用词汇封闭” - 只读“副作用功能”。

答案 2 :(得分:1)

他只是严格遵守他的纯度规则 - 函数每次调用时都必须返回相同的值,并且该问题中的函数f()显然不是。它返回不同的值,因为它会改变其包含的变量。

但是一个变异的词汇闭包不需要被视为副作用,只要它的词汇封闭变量的变化是可以由任何外部实体直接观察到除了函数本身 - 这些变量是封装的,隐藏。

Haskell做同样的事情,没有使用任何monad,这里:

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

编译器可以通过重用fibgen调用的堆栈帧来将其编译为“副作用闭包”,从而实现turning it into a generator

说“绝对不应该进行突变”就像是说“不应该执行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()方法。没错。