7

return昨夜、関数からの /redo オプションについて学びました。別の関数を返すことができます。この関数は呼び出し側で呼び出され、同じ位置からエバリュエーターを再度呼び出します。

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

>> foo "Hello" 10
Hello
20

fooは 1 つの引数しかとらない関数ですが、2 つの引数を取る関数のように動作します。そうでなければ、呼び出し元が関数を返すことを知っている必要があり、その呼び出し元はそのdoエバリュエーターを手動で使用する必要があります。

したがって、なしreturn/redoでは、次のようになります。

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

>> foo "Hello" 10
Hello
== 10

foo1 つのパラメーターを消費し、値によって関数を返しました (呼び出されなかったため、インタープリターは先に進みました)。次に、式は 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"]

通常のリターンと同じように、ブロックを値で返しただけです。「テスト」を出力すべきではありませんか?doそれは...ええと、それで何をするかです:

>> do [print "test"]
test
4

2 に答える 2

7

簡単に言えば、コール ポイントでブロックを評価する必要は一般にないためです。Rebol のブロックはパラメーターをとらないため、どこで評価されるかはほとんど問題になりません。ただし、その「ほとんど」には説明が必要かもしれません...

それは、Rebol の 2 つの興味深い機能、つまり静的バインディングと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]]

同じですが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]]

コード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あります。

ただし、これは興味深い点をもたらします。同じ仕事return/redoをするのでブロックが必要ない場合、関数にも同じことが当てはまりますか? doなぜ必要なreturn/redoのですか?

関数の DO の仕組み

基本的に、関数のreturn/redo実装に使用するのとまったく同じコードを使用するためです。do気付いていないかもしれませんがdo、関数は本当に珍しいものです。

関数値を呼び出すことができるほとんどのプログラミング言語では、R3 の関数がどのように機能するかのように、パラメータを完全なセットとして関数に渡す必要がありますapply。通常の Rebol 関数の呼び出しでは、未知の事前評価規則を使用して、その引数に対して未知の事前評価の回数が追加で発生します。エバリュエーターは実行時にこれらの評価ルールを把握し、評価の結果を関数に渡すだけです。関数自体は、そのパラメーターの評価を処理しないか、それらのパラメーターがどのように評価されたかを必ずしも認識していません。

ただし、do関数値を明示的に指定する場合、関数値を別の関数 (という名前の通常の関数) への呼び出しに渡すことを意味し、関数にまったく渡されなかったdo追加のパラメーターの評価を魔法のように引き起こします。do

それは魔法ではありませんreturn/redodo関数が機能する方法は、通常のショートカット戻り値で関数への参照を返し、ショートカット戻り値にフラグを付けて、返された関数を評価するために呼び出さ doれたインタープリターに、あたかもそこで呼び出されたかのように伝えることです。コードで。これは基本的にトランポリンと呼ばれるものです。

ここで、Rebol のもう 1 つの興味深い機能に到達します。関数から戻り値をショートカットする機能はエバリュエーターに組み込まれていますが、実際にはreturn関数を使用してそれを行うわけではありません。Rebol コードから見られるすべての関数は、内部のもの、さらにreturndo. 呼び出す関数は、これらのreturnショートカットの戻り値の 1 つを生成して返すだけです。残りは評価者が行います。

したがって、この場合、実際に起こったことは、最初から内部で行うことを行うコードがあったということですが、カールは、内部コードがそうする必要がないにもかかわらず、そのフラグを設定return/redoするオプションを関数に追加することを決定しました。内部コードは内部関数を呼び出します。そして、彼は、オプションを外部から利用できるようにすること、またはその理由、またはそれが何をしたかについて、誰にも言いませんでした (すべてを言及することはできないと思います。誰が時間がありますか?)。Carl との会話と、私たちが修正してきたいくつかのバグに基づいて、R2 が機能を別の方法で処理したのではないかと疑っています。returnreturndoreturn/redo

これは、 の処理がreturn/redo完全に関数評価に向けられていることを意味します。これが存在する理由のすべてだからです。それにオーバーヘッドを追加するdoと、関数のオーバーヘッドが追加され、それを頻繁に使用します。利益がほとんどなく、まったく利益を得られないことを考えると、おそらくそれをブロックに拡張する価値はありません.

とはreturn/redoいえ、機能としては、考えれば考えるほど便利になっているようです。最終、私たちはこれが可能にするあらゆる種類のトリックを考え出しました。トランポリンは便利です

于 2013-02-08T00:30:35.477 に答える
4

質問はもともとreturn/redoブロックを評価しなかった理由を尋ねていましたが、次のような定式化もありました。考えれば考えるほど役に立つ」。

私はこれらが真実だとは思いません。私の最初の例は、実際に使用return/redo できるreturn/redoケースを示しています。例は、いわばの「専門分野」にあります。これは、 と呼ばれる可変長合計関数sumnです。

use [result collect process] [
    collect: func [:value [any-type!]] [
        unless value? 'value [return process result]
        append/only result :value
        return/redo :collect
    ]
    process: func [block [block!] /local result] [
        result: 0
        foreach value reduce block [result: result + value]
        result
    ]
    sumn: func [] [
        result: copy []
        return/redo :collect
    ]
]

これは使用例です:

>> sumn 1 * 2 2 * 3 4
== 12

「無制限の数」の引数をとる可変個引数関数は、Rebol では一見しただけでは役に立ちません。たとえばsumn、小さなスクリプトで関数を使用する場合は、引数の収集を停止する場所を示すために、関数を括弧で囲む必要があります。

result: (sumn 1 * 2 2 * 3 4)
print result

これは、eg と呼ばれるより標準的な (非可変個の) 代替手段を使用し、block-sum1 つの引数 (ブロック) だけを取るよりも優れているわけではありません。使用法は次のようになります

result: block-sum [1 * 2 2 * 3 4]
print result

もちろん、関数が括弧を使わずに最後の引数が何であるかを何らかの方法で検出できれば、本当に何かを得ることができます。この場合、#[unset!]値をsumn停止引数として使用できますが、それも入力を惜しみません:

result: sumn 1 * 2 2 * 3 4 #[unset!]
print result

ラッパーの例を見ると、ラッパーreturnreturn/redoはあまり適していないと言えます。ラッパーは専門分野外です。それを実証するために、Rebol 2 で書かれたラッパーを次に示しますが、これは実際にはの専門分野外です。returnreturnreturnreturn/redo

myreturn: func [
    {my RETURN wrapper returning the string "indefinite" instead of #[unset!]}
    ; the [throw] attribute makes this function a RETURN wrapper in R2:
    [throw]
    value [any-type!] {the value to return}
] [
    either value? 'value [return :value] [return "indefinite"]
]

R2 でのテスト:

>> do does [return #[unset!]]
>> do does [myreturn #[unset!]]
== "indefinite"
>> do does [return 1]
== 1
>> do does [myreturn 1]
== 1
>> do does [return 2 3]
== 2
>> do does [myreturn 2 3]
== 2

return/redoまた、テールコールの最適化に役立つというのは真実ではないと思います。www.rebol.orgサイトreturn/redoには、使用せずにテール コールを実装する方法の例があります。前述のように、可変引数関数の実装をサポートするように調整されており、引数の受け渡しに関する限り、他の目的には十分な柔軟性がありません。return/redo

于 2013-02-15T07:53:03.563 に答える