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

Hos*_*ork 7 return rebol trampolines tail-call-optimization rebol3

昨晚我return从一个函数中了解了/ redo选项.它允许您返回另一个函数,然后在调用站点调用该函数并从同一位置重新调用求值程序

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

>> foo "Hello" 10
Hello
20
Run Code Online (Sandbox Code Playgroud)

即使foo是只接受一个参数的函数,它现在就像一个带有两个参数的函数.这样的事情会要求调用者知道你正在返回一个函数,并且调用者必须手动使用do它上面的求值程序.

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

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

>> foo "Hello" 10
Hello
== 10
Run Code Online (Sandbox Code Playgroud)

foo使用了它的一个参数并按值返回了一个函数(没有被调用,因此解释器继续运行).然后表达式评估为10.如果return/redo不存在,你必须写:

>> do foo "Hello" 10
Hello
20
Run Code Online (Sandbox Code Playgroud)

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

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

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

>> foo 10
Leaving...
== 20
Run Code Online (Sandbox Code Playgroud)

但是功能并不是唯一有行为的东西do.因此,如果这是"在呼叫现场消除DO需求"的一般模式,那么为什么不打印任何东西呢?

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

>> test 
== [print "test"]
Run Code Online (Sandbox Code Playgroud)

它只是按值返回块,就像正常返回一样.它不应该打印出"测试"吗?这就是do......呃,用它做的事:

>> do [print "test"]
test
Run Code Online (Sandbox Code Playgroud)

Bri*_*anH 7

简短的回答是,因为它是通常不需要评估在调用点块,因为在雷博尔块不带参数,因此大多没有关系,他们进行评估.但是,"大多数"可能需要一些解释......

它归结为Rebol的两个有趣特性:静态绑定,以及do函数如何工作.

静态绑定和范围

Rebol没有范围的单词绑定,它有静态直接单词绑定.有时似乎我们有词法范围,但我们真的假装通过每次我们构建一个新的"范围"代码块时更新静态绑定.我们也可以随时手动重新绑定单词.

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

然而,这是它变得棘手的地方,功能上下文很奇怪.虽然绑定到函数上下文的单词的绑定是静态的,但是分配给这些单词的值集动态范围的.这是Rebol中如何评估代码的副作用:其他语言中的语言语句是Rebol中的函数,因此if例如,调用实际上会将一个数据块传递给if函数,if然后传递给该函数do.这意味着当一个函数正在运行时,do必须从最近调用的调用帧中查找其单词的值到尚未返回的函数.

这意味着如果您调用一个函数并返回一个带有绑定到其上下文的单词的代码块,那么在该函数返回后评估该块将失败.但是,如果您的函数调用自身并且调用返回一个代码块并且其单词与其绑定,则在函数返回之前评估块将使其在函数的当前调用的调用框架中查找这些单词.

这是相同的你是否do还是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]]
Run Code Online (Sandbox Code Playgroud)

相同,但有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]]
Run Code Online (Sandbox Code Playgroud)

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

>> do f: function [x] [a: 10 either zero? x [do f 1] [a: 20 [a]]] 0
== 10
Run Code Online (Sandbox Code Playgroud)

相同,但有return/redo和函数中的代码:

>> do f: function [x] [a: 10 either zero? x [f 1] [a: 20 return/redo does [a]]] 0
== 10
Run Code Online (Sandbox Code Playgroud)

所以简而言之,对于块来说,除了定义块之外,在其他地方执行块通常没有任何优势,如果你想要它,则更容易使用另一个调用do.需要返回代码以在同一函数的外部调用中执行的自调用递归函数是我从未在Rebol代码中使用的极其罕见的代码模式.

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

然而,这提出了一个有趣的观点:如果你不需要return/redo块,因为do做同样的工作,不一样适用于函数吗?为什么我们需要return/redo呢?

函数的DO如何工作

基本上,我们有,return/redo因为它使用与我们用于实现do函数完全相同的代码.你可能没有意识到,但do功能真的很不寻常.

在大多数可以调用函数值的编程语言中,您必须将参数作为完整集传递给函数,以及R3 apply函数的工作方式.常规的Rebol函数调用会导致使用未知提前评估规则对其参数进行一些未知的提前数量的附加评估.评估者在运行时计算出这些评估规则,并将评估结果传递给函数.函数本身不处理其参数的评估,甚至不知道如何评估这些参数.

但是,当您do明确地显示一个函数值时,这意味着将函数值传递给另一个函数,一个名为的常规函数的调用do,然后神奇地导致评估甚至根本没有传递给do函数的其他参数.

嗯,这不是魔术,它是return/redo.该方式do的功能的工作原理是,它返回一个普通的快捷方式,返回值函数的引用,具有快捷,返回值的标志,告诉该解释称为 do评估返回的功能,如果它被称为在那里在代码中.这基本上就是所谓的蹦床.

这里是我们获得Rebol的另一个有趣特性的地方:快速返回函数值的能力内置于求值程序中,但它实际上并没有使用该return函数来执行它.所有从Rebol的代码,你看到的功能是围绕着内部的东西包装,甚至returndo.return我们调用的函数只生成其中一个快捷方式返回值并返回它; 评估员完成其余的工作.

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

这确实意味着处理return/redo完全面向功能评估,因为这完全是存在的原因.添加任何开销会增加开销do的功能,而我们使用一个不少.可能不值得将它扩展到块,因为我们获得的收益很少,而且我们很少能获得任何好处.

但是对于return/redo一个函数来说,我们对它的思考越多,它似乎越来越有用.在最后一天,我们已经提出了各种各样的技巧.蹦床很有用.