20

タイプは、または関数module!よりも保護された名前空間に優れた構造を提供することを理解しています。モジュール内で単語がどのようにバインドされているか — バインドされていない単語に関連するいくつかのエラーに気付きました。object!'use

REBOL [Type: 'module] set 'foo "Bar"

'fooまた、Rebol はモジュールにローカルな単語 ( ) とシステム関数の単語 ( )をどのように区別し'setますか?

マイナーアップデート、直後:

バインディングの方法を変更するスイッチがあることがわかります。

REBOL [Type: 'module Options: [isolate]] set 'foo "Bar"

これは何が違うのですか?デフォルトでこのメソッドを使用する際の落とし穴は何ですか?

4

1 に答える 1

22

OK、これは少し注意が必要です。

Rebol 3には、システムワードなどはなく、ワードだけがあります。libランタイムライブラリにいくつかの単語が追加されましたset。これはそれらの単語の1つであり、たまたま関数が割り当てられています。モジュールはから単語をインポートしますがlib、「インポート」の意味はモジュールオプションによって異なります。それはあなたが予想していたよりも難しいかもしれないので、説明させてください。

通常のモジュール

手始めに、オプションが指定されていない「通常の」モジュールのインポートの意味について説明します。最初のモジュールから始めましょう:

REBOL [Type: 'module] set 'foo "Bar"

まず第一に、ここでは間違った仮定があります。単語fooはモジュールに対してローカルではなく、と同じsetです。ローカルワードとして定義するfoo場合は、オブジェクトの場合と同じ方法を使用する必要があります。そのワードを、次のようにトップレベルのセットワードとして使用します。

REBOL [Type: 'module] foo: "Bar"

fooとの唯一の違いはset、単語foolibまだエクスポートまたは追加していないことです。ローカルワードとして宣言していないモジュール内のワードを参照する場合、その値やバインディングをどこかから取得する必要があります。通常のモジュールの場合、lib最初にコードをバインドし、次にコードをモジュールのローカルコンテキストに再度バインドすることでそれをオーバーライドします。ローカルコンテキストで定義された単語はすべてそれにバインドされます。ローカルコンテキストで定義されていない単語は、古いバインディング(この場合は。)を保持しますlib。これが、通常のモジュールの「インポート」の意味です。

最初の例では、自分でそうしなかったと仮定すると、その単語fooは事前にランタイムライブラリに追加されていません。つまり、これはfooにバインドされてlibおらず、ローカルワードとして宣言されていないため、ローカルコンテキストにもバインドされていません。その結果、foo何にも縛られませんでした。あなたのコードではそれはエラーでしたが、他のコードではそうではないかもしれません。

分離されたモジュール

モジュールがものをインポートする方法を変更する「分離」オプションがあり、それを「分離」モジュールにします。ここで2番目の例を使用してみましょう:

REBOL [Type: 'module Options: [isolate]] set 'foo "Bar"

分離されたモジュールが作成されると、ネストされたコードであっても、モジュール内のすべての単語がモジュールのローカルコンテキストに収集されます。この場合、それはsetfooがローカルワードであることを意味します。これらの単語の初期値はlib、モジュールの作成時に使用されていた値に設定されます。つまり、単語が定義されている場合libです。単語に値がない場合lib、モジュールにも最初は値がありません。

この値のインポートは1回限りのことであることに注意することが重要です。最初のインポート後、モジュールの外部で行われたこれらの単語への変更は、モジュール内の単語には影響しません。そのため、モジュールは「分離」されていると言えます。コード例の場合、誰かが変更する可能性がlib/setあり、コードに影響を与えないことを意味します。

しかし、あなたが見逃した別の重要なモジュールタイプがあります...

スクリプト

Rebol 3では、スクリプトは別の種類のモジュールです。スクリプトとしてのコードは次のとおりです。

REBOL [] set 'foo "Bar"

または、必要に応じて、スクリプトヘッダーはRebol3ではオプションであるためです。

set 'foo "Bar"

スクリプトは、から単語libをインポートし、それらを分離されたコンテキストにインポートしますが、ひねりがあります。すべてのスクリプトは、「ユーザー」コンテキストと呼ばれる同じ分離されたコンテキストを共有します。これは、スクリプト内の単語の値を変更すると、その単語を使用する次のスクリプトが開始時に変更を確認することを意味します。したがって、上記のスクリプトを実行した後、次のスクリプトを実行しようとすると、次のようになります。

print foo

次に、で定義されていなくfooても、未定義ではなく「バー」を出力します。Rebol 3をインタラクティブに使用し、コンソールにコマンドを入力して結果を取得している場合、入力するすべてのコマンドラインが個別のスクリプトであることを知っておくと面白いかもしれません。したがって、セッションが次のようになっている場合:foolib

>> x: 1
== 1
>> print x
1

x: 1行とprint x行は別々のスクリプトであり、2番目のスクリプトは最初のスクリプトによってユーザーコンテキストに加えられた変更を利用します。

ユーザーコンテキストは実際にはタスクローカルであると想定されていますが、今のところそれは無視しましょう。

なぜ違いがあるのですか?

ここで「システム機能」に戻りますが、Rebolにはそれらがありません。このset関数は他の関数とまったく同じです。実装方法は異なる場合がありますが、それでも通常の単語に割り当てられる通常の値です。アプリケーションはこれらの単語の多くを管理する必要があるため、モジュールとランタイムライブラリがあります。

アプリケーションには、変更する必要のあるものと、変更する必要のないものがあり、どのようなものがアプリケーションによって異なります。物事を整理したり、アクセス制御のために、物事をグループ化する必要があります。グローバルに定義されたものとローカルに定義されたものがあり、グローバルなものをローカルの場所に、またはその逆に取得するための体系的な方法が必要になります。同じ名前。

Rebol 3では、利便性とアクセス制御のために、モジュールを使用してものをグループ化します。lib他のモジュールやユーザーコンテキストのようにローカルの場所にインポートされるものを制御するために、モジュールのエクスポートを収集し、競合を解決する場所としてランタイムライブラリを使用します。いくつかのものをオーバーライドする必要がある場合は、ランタイムライブラリを変更し、必要に応じて変更をユーザーコンテキストに伝達することでこれを行います。実行時にモジュールをアップグレードして、新しいバージョンのモジュールで古いバージョンによってエクスポートされた単語をオーバーライドすることもできます。

通常のモジュールの場合、物事が上書きまたはアップグレードされると、モジュールはそのような変更の恩恵を受けます。これらの変更がメリットであると仮定すると、これは良いことです。通常のモジュールは、他の通常のモジュールおよびスクリプトと連携して、作業する共有環境を作成します。

ただし、これらの種類の変更から分離する必要がある場合があります。おそらく、特定のバージョンの関数が必要であり、アップグレードしたくない場合があります。おそらく、モジュールは信頼性の低い環境にロードされ、コードがハッキングされたくないでしょう。おそらく、もっと予測可能にする必要があるだけです。このような場合、モジュールをこれらの種類の外部変更から分離することをお勧めします。

分離されることの欠点は、必要なランタイムライブラリに変更があった場合、それらを取得できないことです。モジュールに何らかの方法でアクセスできる場合(名前を付けてインポートした場合など)、誰かがそれらの変更をあなたに伝播できる可能性がありますが、アクセスできない場合は運が悪いです。うまくいけば、必要な変更を監視するか、直接lib参照することを考えました。lib

それでも、私たちは別の重要な問題を見逃しました...

エクスポート

ランタイムライブラリとこれらすべてのローカルコンテキストを管理する他の部分は、エクスポートです。あなたはどういうわけかあなたのものをそこに出さなければなりません。そして、最も重要な要素は、疑うことのないものです。つまり、モジュールに名前があるかどうかです。

Rebol3のモジュールの名前はオプションです。最初は、これはモジュールの作成を簡単にする方法のように思えるかもしれません(そして、Carlの最初の提案では、それがまさにその理由です)。しかし、名前が何であるかという理由だけで、そうでないときはできない名前を持っているときにできることがたくさんあることがわかりました。何かを参照する方法です。名前がない場合は、何かを参照する方法がありません。

些細なことのように思えるかもしれませんが、名前でできることがいくつかあります。

  • モジュールがロードされているかどうかがわかります。
  • モジュールが1回だけロードされることを確認できます。
  • モジュールの古いバージョンが以前にあったかどうかを確認し、おそらくそれをアップグレードすることができます。
  • 以前にロードされたモジュールにアクセスできます。

カールが名前をオプションにすることを決めたとき、彼は私たちに、あなたがそれらのことのどれもできないモジュールを作ることが可能であるという状況を与えました。モジュールのエクスポートはランタイムライブラリで収集および整理されることを目的としていたため、ライブラリに影響を与えて簡単に検出できず、モジュールがインポートされるたびにリロードされるという状況が発生しました。

そのため、安全のために、ランタイムライブラリを完全に切り取り、これらの名前のないモジュールから、それらをインポートしていたローカル(モジュールまたはユーザー)コンテキストに直接単語をエクスポートすることにしました。これにより、これらのモジュールは、ターゲットコンテキストによって所有されているかのように、事実上プライベートになります。私たちは潜在的に厄介な状況を取り、それを機能にしました。

privateオプションで明示的にサポートすることにしたのは、このような機能でした。これを明示的なオプションにすると、名前がないという最後の問題に対処するのに役立ちます。プライベートモジュールを何度もリロードする必要がなくなります。モジュールに名前を付けた場合でも、そのエクスポートはプライベートにすることができますが、エクスポートするもののコピーが1つだけ必要です。

ただし、名前付きかどうか、プライベートかどうか、つまり3つのエクスポートタイプです。

通常の名前付きモジュール

このモジュールを見てみましょう:

REBOL [type: module name: foo] export bar: 1

これをインポートすると、デフォルトバージョンが0.0.0のモジュールがロード済みモジュールリストに追加され、1ワードbarがランタイムライブラリにエクスポートされます。この場合の「エクスポート」とはbar、ランタイムライブラリに単語がない場合はその単語を追加し、その単語の実行が終了した後lib/barの値にその単語を設定することを意味します(まだ設定されていない場合)。foo/barfoo

この自動エクスポートは、本体のfoo実行が終了したときに1回だけ発生することに注意してください。foo/barその後に変更を加えても、影響はありませんlib/bar。変更したい場合lib/barは、手動で行う必要があります。

インポートlib/barする前にすでに存在している場合は、別の単語が追加されないことにも注意してください。fooまた、lib/barがすでに値に設定されている(未設定ではない)場合、インポートfooによって既存の値が上書きされることはありません。早い者勝ち。の既存の値を上書きする場合はlib/bar、手動で上書きする必要があります。これは、libオーバーライドを管理するために使用する方法です。

ランタイムライブラリが提供する主な利点は、エクスポートされたすべての単語を1か所で管理し、競合やオーバーライドを解決できることです。ただし、もう1つの利点は、ほとんどのモジュールとスクリプトが実際に何をインポートしているのかを言う必要がないことです。ランタイムライブラリに必要なすべての単語が事前に適切に入力されている限り、後でロードするスクリプトまたはモジュールは問題ありません。これにより、スタートアップコードに一連のインポートステートメントとオーバーライドを簡単に配置して、コードの残りの部分に必要なすべてのものを設定できます。これは、アプリケーションコードの整理と記述を容易にすることを目的としています。

名前付きプライベートモジュール

場合によっては、メインのランタイムライブラリに自分のものをエクスポートしたくないことがあります。スタッフはlibすべてにインポートされるlibため、一般に利用可能にしたいものだけをエクスポートする必要があります。必要なコンテキストのデータのみをエクスポートするモジュールを作成したい場合があります。関連するモジュール、一般的な機能、ユーティリティモジュールなどがある場合があります。この場合、プライベートモジュールを作成することをお勧めします。

このモジュールを見てみましょう:

REBOL [type: module name: foo options: [private]] export bar: 1

このモジュールをインポートしても、には影響しませんlib。代わりに、そのエクスポートは、ターゲットがインポートしている他のプライベートモジュールのエクスポートとともに、このモジュールをインポートしているモジュールまたはユーザーコンテキストに対してローカルなプライベートランタイムライブラリに収集され、そこからターゲットにインポートされます。プライベートランタイムライブラリは、に使用されるのと同じ競合解決にlib使用されます。メインランタイムライブラリはプライベートライブラリlibよりも優先されるため、プライベートライブラリがグローバルなものをオーバーライドすることを期待しないでください。

この種のものは、ユーティリティモジュール、高度なAPI、または他のそのようなトリックを作成するのに役立ちます。それがあなたが興味を持っているのであれば、明示的なインポートを必要とする強力なモジュラーコードを作成するのにも役立ちます。

モジュールが実際に何もエクスポートしない場合、名前付きプライベートモジュールと名前付きパブリックモジュールの間に違いはないため、基本的にパブリックとして扱われることに注意してください。重要なのはそれが名前を持っているということです。それは私たちをもたらします...

名前のないモジュール

上で説明したように、モジュールに名前がない場合は、ほとんどプライベートとして扱う必要があります。ただし、プライベート以上に、ロードされているかどうかがわからないため、アップグレードしたり、リロードを阻止したりすることはできません。しかし、それがあなたが望むものである場合はどうなりますか?

場合によっては、コードを実行して効果を上げる必要があります。このような場合、毎回コードを再実行することがあなたのやりたいことです。多分それはあなたが実行しているスクリプトですが、do単語の漏れを避けるためにモジュールとして構造化されています。たぶん、あなたはミックスインを作っているのかもしれません。初期化が必要なローカル状態を持ついくつかのユーティリティ関数です。それはほとんど何でもありえます。

%rebol.rファイルのエクスポート内容と方法をより細かく制御したいので、ファイルを名前のないモジュールにすることがよくあります。さらに、それは効果のために行われ、リロードまたはアップグレードする必要がないため、名前を付ける意味はありません。

コード例は必要ありません。以前のコード例はこのように動作します。

これで、R3のモジュールシステムの設計の概要が十分に理解できることを願っています。

于 2013-01-27T21:54:11.603 に答える