良い質問です。
Rebol コードは、実際には非常に様式化されたデータ構造と考えるのが一番です。そのデータ構造は「たまたま実行可能」です。しかし、それがどのように機能するかを理解する必要があります。
たとえば、@WiseGenius の提案から:
make-password: func[Length] [
chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
password: copy ""
loop Length [append password (pick chars random Length)]
password
]
を含むブロックを見てくださいappend password...
。そのブロックはそこで「イメージ化」されます。ボンネットの下で実際にどのように見えるかは次のとおりです。
chars: **pointer to string! 0xSSSSSSS1**
password: copy **pointer to string! 0xSSSSSSS2**
loop Length **pointer to block! 0xBBBBBBBB**
password
すべてのシリーズは、インタープリターによってロードされると、このように機能します。文字列、ブロック、バイナリ、パス、括弧など。「タートルズ ダウン」であることを考えると、そのポインターをたどると、ブロック 0xBBBBBBBB は内部的に次のようになります。
append password **pointer to paren! 0xPPPPPPPP**
この結果の 1 つは、シリーズを複数の場所で参照できる (したがって「イメージ化」できる) ことです。
>> inner: [a]
>> outer: reduce [inner inner]
[[a] [a]]
>> append inner 'b
>> probe outer
[[a b] [a b]]
これは、初心者にとっては混乱の元になる可能性がありますが、データ構造を理解すると、いつ COPY を使用するかがわかるようになります。
これが関数で興味深い意味を持っていることに気付きました。このプログラムを考えてみましょう:
foo: func [] [
data: []
append data 'something
]
source foo
foo
foo
source foo
これにより、驚くべき結果が得られます。
foo: func [][
data: []
append data 'something
]
foo: func [][
data: [something something]
append data 'something
]
foo
数回呼び出すと、関数のソース コードが変更されているように見えます。ある意味で、自己変更コードです。
これが気になる場合は、R3-Alpha に攻撃用のツールがあります。PROTECT を使用して、関数本体を変更から保護したり、FUNC や FUNCTION などのルーチンに代わる独自のルーチンを作成して、それを実行することもできます。(PFUNC? PFUNCTION?) Rebol バージョン 3 では、次のように記述できます。
pfunc: func [spec [block!] body [block!]] [
make function! protect/deep copy/deep reduce [spec body]
]
foo: pfunc [] [
data: []
append data 'something
]
foo
それを実行すると、次のようになります。
*** ERROR
** Script error: protected value or series - cannot modify
** Where: append foo try do either either either -apply-
** Near: append data 'something
そのため、シリーズをコピーする必要があります。また、FUNC は単なる関数であることも指摘しています。それ自体、そして FUNCTION も同様です。独自のジェネレーターを作成できます。
これはあなたの脳を壊し、「これはソフトウェアを書くのにまともな方法ではありません」と叫ぶかもしれません。または、「なんてことだ、星がいっぱいだ」と言うかもしれません。反応は異なる場合があります。しかし、これは、システムに力を与え、システムに大きな柔軟性を与える「トリック」のかなり基本的なものです。
(注: Rebol3 の Ren-C ブランチでは、基本的に、関数本体 (および一般的なソース シリーズ) がデフォルトでロックされるようになっています。関数内に静的変数が必要な場合は、次のように言うことができfoo: func [x <static> accum (copy "")] [append accum x | return accum]
、関数は累積します。accum
通話中の状態)。
また、各実行で実際に何が起こっているかに細心の注意を払うことをお勧めします。foo
関数を実行する前は、データには値がありません。関数を実行するたびに、エバリュエーターが SET-WORD を確認します。その後に系列値が続くと、変数への割り当てが実行されます。
data: **pointer to block! 0xBBBBBBBB**
その割り当ての後、存在するブロックへの2 つの参照があります。1 つは、関数が実行される前に、LOAD 時に確立されたコード構造内に存在することです。2 番目の参照は、データ変数に格納されたものです。このシリーズを変更しているのは、この 2 番目の参照を通じてです。
また、関数が実行されるたびにデータが再割り当てされることに注意してください。しかし、何度も何度も同じ値に再割り当てされます...その元のブロックポインター! これが、実行ごとに新しいブロックが必要な場合に COPY を実行する必要がある理由です。
評価ルールの根底にある単純さを把握することは、目がくらむような面白さの一部です。これは、言語を作成するためにシンプルさがドレスアップされた方法です (独自の手段にひねることができる方法で)。たとえば、「複数割り当て」はありません。
a: b: c: 10
それは、エバリュエーターが SET-WORD として a :をヒットしただけです! 記号を使用して、「よし、バインディング コンテキスト内の変数aを、次の完全な式が生成するものと関連付けましょう」と言っています。 b:同じことをします。 c:は同じことを行いますが、整数値 10 のために端末にヒットします...そしてまた10に評価されます。したがって、複数代入のように見えます。
したがって、一連のリテラルの元のインスタンスは、ロードされたソースにぶら下がっているものであることを覚えておいてください。評価者がこの種の SET-WORD を実行することに成功した場合! またはSET代入の場合、ソース内のそのリテラルへのポインターを借りて、変数に突っ込みます。これは変更可能な参照です。あなた (またはあなたが設計した抽象化) は、PROTECT または PROTECT/DEEP を使用して不変にすることができ、COPY または COPY/DEEP を使用して非参照にすることができます。
関連メモ
copy [] ... と書くべきではないと主張する人もいます。なぜなら、(a) COPY を書くのを忘れる癖がつくかもしれないし、(b) そうするたびに未使用のシリーズを作っているからです。その「空白シリーズ テンプレート」が割り当てられ、ガベージ コレクターによってスキャンされる必要があり、誰も実際にそれに触れることはありません。
書いたらメイクブロック!10(またはブロックを事前に割り当てたい任意のサイズ)の問題を回避し、シリーズを保存し、サイジングのヒントを提供します.