为什么 return/redo 在调用上下文中评估结果函数,但不评估块结果?

2023-11-22

昨晚我了解了 /redo 选项,当你return来自一个函数。它可以让你回来another函数,然后在调用站点调用该函数并从同一位置重新调用评估器

>> foo: func [a] [(print a) (return/redo (func [b] [print b + 10]))] 

>> foo "Hello" 10
Hello
20

虽然foo是一个只接受一个参数的函数,现在它其行为就像一个带有两个参数的函数。否则,类似的事情会要求调用者知道您正在返回一个函数,并且该调用者必须手动使用do评估者对其进行评估。

因此没有return/redo,你会得到:

>> foo: func [a] [(print a) (return (func [b] [print b + 10]))] 

>> foo "Hello" 10
Hello
== 10

foo消耗了它的一个参数并按值返回一个函数(该函数没有被调用,因此解释器继续前进)。然后表达式的计算结果为 10。如果return/redo不存在你必须写:

>> do foo "Hello" 10
Hello
20

这使得调用者不必知道(或关心)您是否选择返回要执行的函数。这很酷,因为您可以执行诸如尾部调用优化之类的操作,或者为返回功能本身编写包装器。这是一个变体return打印一条消息但仍然退出该函数并提供结果:

>> myreturn: func [] [(print "Leaving...") (return/redo :return)]

>> foo: func [num] [myreturn num + 10]

>> foo 10
Leaving...
== 20

但函数并不是唯一具有行为的东西do。所以如果这是一个通用模式“消除了呼叫站点对 DO 的需要”,那为什么不打印任何东西呢?

>> test: func [] [return/redo [print "test"]]

>> test 
== [print "test"]

它只是按值返回块,就像正常的返回一样。它不应该打印出“test”吗?就是这样do会...呃,用它做:

>> do [print "test"]
test

简短的答案是因为通常不需要在调用点评估块,因为 Rebol 中的块不带参数,因此它mostly在哪里评估并不重要。然而,这个“大部分”可能需要一些解释......

这归结为 Rebol 的两个有趣的特性:静态绑定,以及如何do一个函数的工作原理。

静态绑定和范围

Rebol 没有作用域字绑定,它有静态直接字绑定。有时,我们似乎拥有词法作用域,但实际上我们通过每次构建新的“作用域”代码块时更新静态绑定来伪造这一点。我们还可以随时手动重新绑定单词。

不过,在这种情况下,这对我们来说意味着,一旦块存在,它的绑定和值就是静态的 - 它们不受块的物理位置或评估位置的影响。

然而,这就是棘手的地方,函数上下文很奇怪。虽然绑定到函数上下文的单词的绑定是静态的,一组值分配给这些词的是动态地范围。这是 Rebol 中代码评估方式的副作用:其他语言中的语言语句是什么,Rebol 中的函数,因此调用if,例如,实际上将一个数据块传递给if函数其中if然后传递到do。这意味着当一个函数运行时,do必须从最近一次调用尚未返回的函数的调用框架中查找其单词的值。

这确实意味着,如果您调用函数并返回一个代码块,其中单词绑定到其上下文,则在函数返回后评估该块将失败。但是,如果您的函数调用itself and that调用返回一个代码块,其单词绑定到它,评估that函数返回之前的块将使其在调用框架中查找这些单词current调用你的函数。

这对于您来说都是一样的do or return/redo,并影响内部功能。让我演示一下:

函数返回在函数返回后计算的代码,引用函数字:

>> a: 10 do do has [a] [a: 20 [a]]
** Script error: a word is not bound to a context
** Where: do
** Near: do do has [a] [a: 20 [a]]

相同,但与return/redo以及函数中的代码:

>> a: 10 do has [a] [a: 20 return/redo does [a]]
** Script error: a word is not bound to a context
** Where: function!
** Near: [a: 20 return/redo does [a]]

Code do版本,但在对同一函数的外部调用内:

>> do f: function [x] [a: 10 either zero? x [do f 1] [a: 20 [a]]] 0
== 10

相同,但与return/redo以及函数中的代码:

>> do f: function [x] [a: 10 either zero? x [f 1] [a: 20 return/redo does [a]]] 0
== 10

简而言之,对于块,除了定义它的地方之外,在其他地方执行块通常没有任何优势,并且如果您愿意,使用另一个调用会更容易do反而。自调用递归函数需要返回要在同一函数的外部调用中执行的代码,这是一种极其罕见的代码模式,我从未在 Rebol 代码中看到过这种模式。

或许可以改变return/redo所以它也可以处理块,但可能不值得增加开销return/redo添加一个仅在极少数情况下有用并且已经有更好的方法的功能do it.

然而,这提出了一个有趣的观点:如果你不需要return/redo对于块,因为do做同样的工作,功能不也一样吗?为什么我们需要return/redo at all?

函数 DO 的工作原理

基本上,我们有return/redo因为它使用与我们用来实现完全相同的代码do的一个函数。你可能没有意识到,但是do一个函数确实不寻常。

在大多数可以调用函数值的编程语言中,您必须将参数作为一个完整的集合传递给函数,就像 R3 的方式一样apply功能有效。常规 Rebol 函数调用会使用未知的提前评估规则对其参数进行一些未知的提前数量的附加评估。评估器在运行时计算出这些评估规则,并将评估结果传递给函数。函数本身不处理其参数的评估,甚至不一定知道how对这些参数进行了评估。

然而,当你do显式函数值,这意味着将函数值传递给调用another函数,一个regular函数名为do,然后神奇地导致附加参数的评估甚至没有传递给do完全发挥作用.

嗯,这不是魔法,而是return/redo。道路do函数的工作原理是,它以常规快捷方式返回值返回对函数的引用,并在快捷方式返回值中带有一个标志,告诉解释器:called do评估返回的函数,就像在代码中调用它一样。这基本上就是所谓的蹦床。

这是 Rebol 的另一个有趣的功能:从函数快捷返回值的能力内置于求值器中,但它实际上并没有使用return函数来做到这一点。您从 Rebol 代码中看到的所有函数都是内部内容的包装器,甚至return and do. The return我们调用的函数只是生成这些快捷方式返回值之一并返回它;评估员会完成剩下的工作。

所以在这种情况下,真正发生的事情是我们一直都有代码做这些事情return/redo内部做,但卡尔决定添加一个选项到我们的return函数来设置该标志,即使内部代码不需要return这样做是因为内部代码调用内部函数。然后他没有告诉任何人他正在向外部提供该选项,或者为什么,或者它做了什么(我猜你不能提及所有内容;谁有时间?)。根据与 Carl 的对话以及我们一直在修复的一些错误,我怀疑 R2 处理了do函数的不同方式,以某种方式使return/redo不可能的。

这确实意味着处理return/redo非常彻底地面向功能评估,因为这是它存在的全部原因。添加任何开销都会增加开销do一个函数,我们使用它lot。可能不值得将其扩展到区块,因为我们获得的收益是如此之少,而且我们获得任何好处的机会又是多么少。

For return/redo不过,我们对函数的思考越多,它似乎就变得越来越有用。在最后day我们想出了各种各样的技巧来实现这一点。蹦床是useful.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 return/redo 在调用上下文中评估结果函数,但不评估块结果? 的相关文章

随机推荐