繁体   English   中英

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

[英]“Side-effecting lexical closure” vs function in Scala

他的回答评论部分 ,Apocalisp陈述如下:

好吧,你确实要求一个功能。 一个侧面有效的[原文如此]词汇封闭显然不是一个功能。

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

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

如果是这种情况,那么Scala本身就会做出这种区分,还是只留给程序员呢? 如果是这样,每个可调用(缺少一个更好的术语)是没有副作用的函数,还是每个可调用的副作用词汇闭包?

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

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

因此,“副作用词汇封闭”只是其中之一,具有副作用。


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

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

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

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

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

我认为你正在寻找的术语是纯粹而不纯的功能

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

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

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

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

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

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

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()方法。 没有错。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM