且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

为什么返回/重做在调用上下文中评估结果函数,但不评估块结果?

更新时间:2023-11-15 08:09:46

简短的答案是因为通常不需要在调用点评估一个块,因为Rebol中的块不采用参数,因此主要是无关紧要.但是,大部分"可能需要一些解释...

The short answer is because it is generally unnecessary to evaluate a block at the call point, because blocks in Rebol don't take parameters so it mostly doesn't matter where they are evaluated. However, that "mostly" may need some explanation...

它归结为Rebol的两个有趣的功能:静态绑定以及函数do的工作方式.

It comes down to two interesting features of Rebol: static binding, and how do of a function works.

Rebol没有作用域内的单词绑定,它具有静态直接单词绑定.有时候,似乎我们有词法范围,但是我们每次构建新的作用域"代码块时都会通过更新静态绑定来伪造这一点.我们还可以根据需要手动重新绑定单词.

Rebol doesn't have scoped word bindings, it has static direct word bindings. Sometimes it seems like we have lexical scope, but we really fake that by updating the static bindings each time we're building a new "scoped" code block. We can also rebind words manually whenever we want.

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

What that means for us in this case though, is that once a block exists, its bindings and values are static - they're not affected by where the block is physically located, or where it is being evaluated.

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

However, and this is where it gets tricky, function contexts are weird. While the bindings of words bound to a function context are static, the set of values assigned to those words are dynamically scoped. It's a side effect of how code is evaluated in Rebol: What are language statements in other languages are functions in Rebol, so a call to if, for instance, actually passes a block of data to the if function which if then passes to do. That means that while a function is running, do has to look up the values of its words from the call frame of the most recent call to the function that hasn't returned yet.

这确实意味着,如果调用函数并返回带有绑定到其上下文的单词的代码块,则该函数返回后,对该块的求值将失败.但是,如果您的函数调用本身并且 that 调用返回绑定了单词的代码块,则在函数返回之前评估那个块将使其在函数的 current 调用的调用框架中查找这些单词.

This does mean that if you call a function and return a block of code with words bound to its context, evaluating that block will fail after the function returns. However, if your function calls itself and that call returns a block of code with its words bound to it, evaluating that block before your function returns will make it look up those words in the call frame of the current call of your function.

对于doreturn/redo,这都是相同的,并且也会影响内部功能.让我演示一下:

This is the same for whether you do or return/redo, and affects inner functions as well. Let me demonstrate:

函数返回的代码,在函数返回后进行评估,并引用一个函数字:

Function returning code that is evaluated after the function returns, referencing a function word:

>> 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和函数中的代码:

Same, but with return/redo and the code in a function:

>> 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]]

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

Code do version, but inside an outer call to the same function:

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

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

Same, but with return/redo and the code in a function:

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

因此,简而言之,对于块而言,在定义位置以外的其他地方进行块通常没有任何好处,如果愿意,可以使用另一个对do的调用来实现.需要返回要在同一函数的外部调用中执行的代码的自调用递归函数是一种极其罕见的代码模式,我从未在Rebol代码中使用过.

So in short, with blocks there is usually no advantage to doing the block elsewhere than where it is defined, and if you want to it is easier to use another call to do instead. Self-calling recursive functions that need to return code to be executed in outer calls of the same function are an exceedingly rare code pattern that I have never seen used in Rebol code at all.

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

It could be possible to change return/redo so it would handle blocks as well, but it probably isn't worth the increased overhead to return/redo to add a feature that is only useful in rare circumstances and already has a better way to do it.

但是,这引出了一个有趣的观点:如果您不需要return/redo块,因为do可以完成相同的工作,那么对函数是否不适用?为什么我们完全需要return/redo?

However, that brings up an interesting point: If you don't need return/redo for blocks because do does the same job, doesn't the same apply to functions? Why do we need return/redo at all?

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

Basically, we have return/redo because it uses exactly the same code that we use to implement do of a function. You might not realize it, but do of a function is really unusual.

在大多数可以调用函数值的编程语言中,您必须将参数作为完整的集合传递给函数,这类似于R3的apply函数的工作方式.常规的Rebol函数调用会使用未知的提前评估规则对其参数进行一些未知的提前评估次数.评估者会在运行时找出这些评估规则,然后将评估结果传递给函数.该函数本身不处理其参数的评估,甚至不必知道如何评估这些参数.

In most programming languages that can call a function value, you have to pass the parameters to the function as a complete set, sort of how R3's apply function works. Regular Rebol function calling causes some unknown-ahead-of-time number of additional evaluations to happen for its arguments using unknown-ahead-of-time evaluation rules. The evaluator figures out these evaluation rules at runtime and just passes the results of the evaluation to the function. The function itself doesn't handle the evaluation of its parameters, or even necessarily know how those parameters were evaluated.

但是,当您明确地do一个函数值时,这意味着将函数值传递给对另一个函数(名为do regular 函数)的调用,然后神奇地导致对甚至根本不会传递给do函数的所有参数进行评估.

However, when you do a function value explicitly, that means passing the function value to a call to another function, a regular function named do, and then that magically causes the evaluation of additional parameters that weren't even passed to the do function at all.

这不是魔术,而是return/redo.函数do的工作方式是,它以常规的快捷键返回值返回对该函数的引用,并且在快捷键返回值中带有一个标志,该标志告诉解释器称为 评估返回的函数,就像在代码中被调用一样.这基本上就是所谓的蹦床.

Well it's not magic, it's return/redo. The way do of a function works is that it returns a reference to the function in a regular shortcut-return value, with a flag in the shortcut-return value that tells the interpreter that called do to evaluate the returned function as if it were called right there in the code. This is basically what is called a trampoline.

在这里,我们可以了解Rebol的另一个有趣功能:评估器内置了从函数中快速返回值的功能,但实际上并没有使用return函数来实现.从Rebol代码中看到的所有功能都是内部内容的包装,甚至是returndo.我们调用的return函数仅生成这些快捷方式返回值之一并返回它;剩下的由评估员来完成.

Here's where we get to another interesting feature of Rebol: The ability to shortcut-return values from a function is built into the evaluator, but it doesn't actually use the return function to do it. All of the functions you see from Rebol code are wrappers around the internal stuff, even return and do. The return function we call just generates one of those shortcut-return values and returns it; the evaluator does the rest.

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

So in this case, what really happened is that all along we had code that did what return/redo does internally, but Carl decided to add an option to our return function to set that flag, even though the internal code doesn't need return to do so because the internal code calls the internal function. And then he didn't tell anyone that he was making the option externally available, or why, or what it did (I guess you can't mention everything; who has the time?). I have the suspicion, based on conversations with Carl and some bugs we've been fixing, that R2 handled do of a function differently, in a way that would have made return/redo impossible.

这确实意味着return/redo的处理完全针对函数评估,因为这完全是存在的全部原因.添加任何开销会增加函数的do开销,而我们使用的是 lot .考虑到我们将获得的收益很少,而我们却很少获得任何收益,可能不值得将其扩展到区块.

That does mean that the handling of return/redo is pretty thoroughly oriented towards function evaluation, since that is its entire reason for existing at all. Adding any overhead to it would add overhead to do of a function, and we use that a lot. Probably not worth extending it to blocks, given how little we'd gain and how rarely we'd get any benefit at all.

对于函数的return/redo来说,我们考虑得越多,它似乎变得越来越有用.在最后的中,我们提出了各种使之可行的技巧.蹦床是有用的.

For return/redo of a function though, it seems to be getting more and more useful the more we think about it. In the last day we've come up with all sorts of tricks that this enables. Trampolines are useful.