web-dev-qa-db-ja.com

言語設計:包含スコープにアクセスする代わりに、識別子の出現をスキップする

この質問に適切なタイトルを書く方法がわかりません。

隠し識別子(変数など)をアクセス可能にする演算子をDSLに導入することを考えています。 JavaまたはC#の_this.foo_を考えて、ローカルのfooによって隠されたメンバーにアクセスします。またはbarの場合は_base.bar_現在のクラスのメンバーこれを「明示的に囲んでいるスコープにアクセスする」と呼びましょう。

DSLの対象読者は、プログラマー/初心者ではないため、作業しているスコープを(詳細に)理解する必要はありません。代わりに、識別子の1回の出現をスキップする演算子を導入することをお勧めします。複数回適用されます。コンパイラーは、識別子を解決するとき、ローカルスコープから開始して外側に移動するときに通常のことを行います。何かを見つけてオペレーターを使うと、彼はその結果を無視して探し続けます。

次の疑似コードの例を考えてみます。

_foo = 1 // global
object bar {
  foo = "member foo" 
  function bla() {
    let foo = 2
    // _ we are here
  }
}
_

ターゲットの場所で、fooは値_2_のローカル変数を参照します。
_@foo_は文字列型のメンバーになります(1回スキップします)
_@@foo_は、値が_1_のグローバル変数になります(2つスキップ)

JavaまたはC#などの言語の代替案は、メンバーの場合は_this.foo_であり、架空のグローバルの場合は_global::foo_です。これに関する問題は、a)ユーザーが学習する必要がありますいくつかのキーワードとb)ユーザーはスコープの正確な構造を理解する必要があります(thisは何を参照するかなど)

実際の質問:これは悪い考えですか、私が見ていないいくつかの深刻な問題がありますか?
それとも、私が調査できるような演算子を採用している言語はありますか?

関連するかもしれないもう少しコンテキスト:

  • これは汎用プログラミング言語ではありません
  • テキスト要素と視覚要素が混在しているため(Excelと考えてください)、何が何に含まれているのか(発生をスキップするとどこに行くのか)が視覚的に明確になります
  • 静的型付け、動的スコープなし(コンパイル時の評価)
  • 人々は自分の物が別の物と同じ名前を持っていることを「理解」するという考えですが、その物のコンテナを明示的に参照するための魔法の言葉を知りません。
  • fooが1つしかない_@foo_を書き込むと、エラーが発生します

更新:いくつかの所見:

  • レキシカルスコープ:これについては言及しませんでしたが、最初に使用すると、演算子はすべてのレキシカルスコープをエスケープします。ループや関数など、3つのネストされたブロックに3つの変数がある場合、_@_を使用してそれらをアドレス指定することはできません。非字句スコープ(オブジェクト、グローバルスコープ)にアクセスすることのみを目的としています。初心者にはレキシカルスコープを理解するのがさらに難しく、そのため、メンバーをアドレス指定する必要がある_@_の数は理解できません。
  • Brittleness: John R. Strohmが述べているように、新しいfooを導入するか、オブジェクトから1つ削除すると、既存のfoo応答へのすべての参照が壊れます。それらに対処するために必要な_@_の数は変更されます。それは確かに問題ですが、例えば、そうではありません。 C#も?この例を考えてみましょう。

_namespace foo {
    class X {
        public static int Value;
    }

    namespace bar {
        class foo { }
        class X {}

        class Main {
            public static int Value = global::foo.X.Value + 1;
        }
    }
}
_

新しいclassを作成することにより、ユーザーは外部スコープ内の他のタイプと名前空間を非表示にできます。既存のコードは機能しなくなり、名前空間名にプレフィックスを付けるか、名前空間が非表示の場合は_global::_のいずれかで更新する必要があります。

重要なのは、この問題はC#などの言語に存在し、問題を引き起こさないことです。 _@_演算子を使用すると、問題がさらに深刻になります。これは、物事を削除すると失敗するためですが、スコープを明示的にアドレス指定すると、問題が解決します。それに対する私の議論はこれです:あなたがものを追加するときにあなたのコードを修正しなければならないとき、あなたがものを削除するときにもそれを修正しなければならないことが予想されます。


更新:ジョンの答えを受け入れたのは、他のほとんどの人が言及した問題よりも悪いと私が信じている問題を指摘したからです(コードを別の場所にコピーすると変更される可能性があります)どのシンボルがアドレス指定されているか)。
問題は、ユーザーが1つのレイヤーで新しいシンボルfooを作成すると、ネストされたレイヤーで解決されるfooシンボルが変更されることです。言い換えれば、現在編集されていないコードへの目に見えない変更です。私の意見では、これはコピー&ペーストの問題よりも悪いです。少なくともその場で対処でき、それでも気づかないうちに、ユーザーがそれを発見する可能性が高くなるからです。

3
enzi

あなたは、動的スコープではなく字句スコープを意図していると述べました。これはいくつかの問題を解決しますが、他のものを作成します。 (GNU Emacs LISPは動的にスコープされ、ユーザーが他の拡張機能の動作を変更する拡張機能を記述できるようにします。)

複数レベルのスコーピングを許可するつもりであるように思われます。「foo」が判明した場合、ユーザーが「@@@@@@@ foo」のようなことを行う必要があり、何かに到達するために半ダースレベル上がる必要があります。本当に人気のある識別子になる。

明らかなトラブルメーカーはこれです:あなたが「foo」を3層上に持っていて、それがそことここの間のどこにも隠されておらず、「foo」と言うだけでそれに到達できる場合、誰かが少なくとも3つの場所がありますそれ以外の場合は、新しい「foo」を定義するだけでコードを壊すことができます。

これは私には災害のレシピのようです。

代わりに、ネストされたプロシージャモデルを破棄することを検討し、代わりに「フラット」モデルに移動することをお勧めします。 「ローカル」変数があり、名前で参照される「foo」などのオブジェクトをインポートし、それらのオブジェクト内のエンティティは「foo.waldo」などの修飾構文を使用します。 (はい、C/C++ポインター構文「foo-> waldo」、またはPascalポインター構文「foo ^ .waldo」を使用することもできます。)

1
John R. Strohm

私が理解しているように、あなたは構文を提案しています:@識別子の前に付けます。 @foo。現在のスコープから外側のスコープに向かって、次の(2番目の)表示されるfooに移動します。

したがって、@fooを書き込むには、スコープ内に2つのfoosが存在する必要があります(一方が他方を非表示にします)。

それはその構造を使用して書かれたコードを意味するので、私はデザインを気にしません。 @fooは、別のfoo(関心のない)の存在の確認をキャプチャして、コードをより脆弱にします。これは、ある見方では、両方のfoosの参照であるためです。

@@fooを使用して記述されたコードは、関心のない他の2つのfoosの存在を暗黙的にキャプチャします。

これは、単純なコードではコピーと貼り付けのエラーが発生しやすくなることを示唆しています。

(さらに、同じスコープのfoobarは、一方には@が必要ですが、もう一方には必要ない場合があります。提供する例のように、両方を修飾するか、まったく修飾しないでください。this.global::。)

4
Erik Eidt

間違いを犯しやすいことを考えてください。たとえば誰かが書いた場合

@@foo = @foo

の代わりに

@foo = @@foo

では、初心者プログラマーがこのエラーを見つけるのにどれくらいの時間がかかると思いますか。

私がもっと理にかなっていると思うのは、あなたがどのfooを見つけやすいかを簡単に見つけることです。

例えば

  • @ global.foo(これはわかりました)
  • bar.foo(これは、barオブジェクトで定義されたfooです)
  • bla.foo(これはbla関数で定義されたfooです)

しかし、それはあなたが本当に外側のスコープにアクセスする必要があると仮定しています。私の意見では、DSLは複雑さを最小限に抑えることに関するものでなければなりません。したがって、このようなものをまったく必要としないツールを導入してください。 DSLは、ビジネスルールを理解する方法で記述する必要があります。スコープを5深くネストしている場合は、間違った方向に進んでいます。

更新:この問題に対処する別の方法は、「通常の」グローバル変数を決して許可しないことです。グローバル変数が常に「global.foo」として定義され、(明示的に)呼び出される場合は、2つのfoo変数間の名前の衝突も回避できます。ユーザーはこのような変数を定義する必要があります

let global.foo = 5

グローバル変数が必要な場合

3
Batavia

このような言語構造は実際には必要ありません。ユーザーが周囲のスコープ内のシンボルにアクセスしたい場合、最初に別の名前で外部スコープ内の外部シンボルのエイリアスを作成できます。

_let foo = "outer"
let outer_foo = foo
{
  let foo = "inner"
  // can use foo and outer_foo
}
_

したがって、ほとんどの主流のプログラミング言語には、ネストされたスコープから外部スコープにアクセスするメカニズムがありません。関連するが完全に異なる:

  • java、C#の_this.foo_などのインスタンスメンバーへのアクセス。 C++では_this->foo_; Rubyでは_@foo_。
  • (グローバル)名前空間(ike _name.space.Symbol_ in Java、C#、Python; _name::space::Symbol_ in C++、Perl)でシンボルを検索します。

注目すべき例外はPythonで、nonlocalおよびglobalキーワードを使用して変数をスコープに入れることができます。ただし、これが必要なのは、Python変数を宣言できないためです。変数に割り当てるたびに、最も内側のスコープで作成されます。

あまり一般的でない言語では、外側のスコープにアクセスするための演算子が存在します。 1つの例は、ファイルパスのような構文を使用するハンドルバーテンプレート言語です。_{{foo}}_は現在のスコープの変数にアクセスし、_{{../foo}}_は親スコープを調べ、_{{../../foo}}_は2番目の親を調べます。スコープなど。 Perl6では、OUTER疑似名前空間を使用すると、_OUTER::Symbol_、_OUTER::OUTER::Symbol_などのように外側を見ることができます。また、_OUTERS::_のように 他の多くの疑似名前空間 があり、「外側のスコープはすべてカウントする必要がない」という意味です。

これは良い考えですか?たぶん。厳密に必要でも良いアイデアでもありませんが、外側のスコープにアクセスすることはおそらく便利です。特に、「字句スコープ」の概念をユーザーに説明したくない場合。私は、Perl6の疑似名前空間ソリューションがブロックスコープにとってここで最もエレガントなアプローチであると思いますが、証拠はありません。 outerのような明示的な名前を使用する方が、言語でPerlまたはRubyのようなSigilをすでに使用している場合を除き、_@_のようなスコープウォーキングシンボルを使用するよりもおそらく優れています。

ただし、これらの明示的な外部スコープには一般的な問題があります。通常、必要なレベルを慎重にカウントする必要があるため、コードをリファクタリングしてコピーアンドペーストするのが難しくなります。コードを別のコンテキストに置くと、スコープレベルのカウントはおそらくオフにする。

オブジェクト指向コードの場合、thisselfmycurrentObjectのような古典的なキーワードの方が賢明かもしれません。この場合、シンボルアクセスは次のように行われます。その他のオブジェクト。

1
amon

いくつかの問題

  1. 原則として、スコープの制御はプログラマにとって非常に重要な関心事です。スコープを使用してすばやく簡単に遊べるようにすると、スコープの制御が不十分になると依存関係のトレースが難しくなるため、複雑さが増す可能性が高くなります。

  2. 相対的な数(@記号の数)でスコープを示すと、コードを移動したり、レイヤーを削除したりするとスコープのレベルが変わる可能性があるため、エラーが発生しやすくなります。言語がどのように構造化されるかはわかりませんが、ifブロックとwhileブロックには独自のスコープがありますか?ネストされたクラスを持つことはできますか?どんな種類のネストでも、特にコードをブロックの内側からそのブロックの外側に移動する場合は、まったく混乱することになります。 @@をすべて@に変更する必要があります。

  3. 最終的に、同じ変数を表す2つのシンボルができます。 @fooおよび@@fooは、コードの異なるレイヤーで使用されている場合、同じメモリ位置を参照する可能性があります。それは私にはまったく混乱しています。

  4. プログラマーがグローバルスコープとは何かを理解していない場合、それを支援する言語構造は宇宙にはありません。

1
John Wu

Perlはあなたが提案したのと同じようなことをすることができますが、そうするように設定する必要があります。この手法は、字句変数と動的変数の両方で機能します。

字句変数の使用:

my $foo = 'begin';
my $foo_ref = \$foo;
print "foo=$foo foo_ref=$$foo_ref # both the same at the beginning\n";

{
    my $foo = 'inner';
    print "foo=$foo foo_ref=$$foo_ref # foo changes but foo_ref remains the same\n";

    # magic
    $$foo_ref = 'magic';
    print "foo=$foo foo_ref=$$foo_ref # foo_ref changes but foo is still inner\n";
}
print "foo=$foo foo_ref=$$foo_ref # outer foo changed when foo_ref did\n";

動的変数の使用:

our $foo = 'begin';
our $foo_ref = \$foo;
print "foo=$foo foo_ref=$$foo_ref # both the same at the beginning\n";

{
    local $foo = 'inner';
    print "foo=$foo foo_ref=$$foo_ref # foo changes but foo_ref remains the same\n";

    # magic
    $$foo_ref = 'magic';
    print "foo=$foo foo_ref=$$foo_ref # foo_ref changes but foo is still inner\n";
}
print "foo=$foo foo_ref=$$foo_ref # outer foo changed when foo_ref did\n";
1
shawnhcorey