左右に2つ以上の派生ツリーが存在する場合、文法があいまいであることを理解しますが、なぜそれがあまりにもひどくて誰もがそれを取り除きたいと思うのか理解できません。
std::vector<std::vector<int>>
は2011年に意図的にあいまいさを追加しました>>
。重要な洞察は、これらの言語はベンダーよりもはるかに多くのユーザーを持っているため、ユーザーにとってささいな煩わしさを修正することは、実装者による多くの作業を正当化することです。
左右に2つ以上の派生ツリーが存在する場合、文法があいまいであることを理解しますが、なぜそれがあまりにもひどくて誰もがそれを取り除きたいと思うのか理解できません。
std::vector<std::vector<int>>
は2011年に意図的にあいまいさを追加しました>>
。重要な洞察は、これらの言語はベンダーよりもはるかに多くのユーザーを持っているため、ユーザーにとってささいな煩わしさを修正することは、実装者による多くの作業を正当化することです。
回答:
算術式のため、以下の文法を考えてみましょう:
左側のものによればをとして解釈する必要があります。これは通常の解釈です。右側の1つによると、これをとして解釈する必要がありますが、これはおそらく意図したものではありません。
プログラムをコンパイルするとき、構文の解釈を明確にする必要があります。これを強制する最も簡単な方法は、明確な文法を使用することです。文法があいまいな場合は、演算子の優先順位や結合性などのタイブレークルールを提供できます。これらの規則は、特定の方法で文法を明確にすることで同等に表現できます。
構文木ジェネレーターを使用して生成された木を解析します。
+
)。
他の既存の回答【とは対照的に1、2 ]、確かに曖昧な文法である適用分野、ある有用。自然言語処理(NLP)の分野で、自然言語(NL)を正式な文法で解析したい場合、NLはさまざまなレベルで本質的に曖昧であるという問題を抱えています[Koh18、ch。6.4]:
構文の曖昧さ:
ピーターは赤いスポーツカーで男を追いかけた
赤いスポーツカーに乗っていたのはピーターですか?
意味的な曖昧さ:
ピーターは銀行に行きました
座る銀行またはお金を引き出す銀行ですか?
実用的な曖昧さ:
二人の男が二つの袋を運んだ
彼らは一緒にバッグを運んだのですか、それとも各人が2つのバッグを運んだのですか
NLPのさまざまなアプローチは、一般に処理、特にこれらの曖昧さを処理します。たとえば、パイプラインは次のようになります。
すべての文に対してこのパイプラインを実行します。たとえば、処理する同じ本のテキストが多いほど、前の文からステップ3まで存続した不可能な余分なモデルを除外できます。
プログラミング言語とは対照的に、すべてのNL文が正確なセマンティクスを持つという要件を手放すことができます。代わりに、大きなテキストの解析全体で複数の可能なセマンティックモデルをブックキープできます。しばらくの間、後の洞察は以前のあいまいさを排除するのに役立ちます。
曖昧な文法の複数の派生を出力できるパーサーで手を汚したい場合は、Grammatical Frameworkを見てください。また、[Koh18、ch。5]は、上記の私のパイプラインに似たものを示す紹介です。ただし、[Koh18]は講義ノートであるため、講義なしではノート自体が簡単に理解できない場合があることに注意してください。
参照資料
[Koh18]:マイケルコールハース。「論理ベースの自然言語処理。冬学期2018/19。講義ノート。」URL:https://kwarc.info/teaching/LBS/notes.pdf。コースの説明のURL:https : //kwarc.info/courses/lbs/(ドイツ語)
[Koh18、ch。5]:[Koh18]の第5章「フラグメントの実装:文法的および論理的フレームワーク」を参照してください。
[Koh18、ch。6.4] [Koh18]の6.4章「あいまいさの計算上の役割」を参照
あいまいさを処理する明確な方法があったとしても(あいまいな式は構文エラーなど)、これらの文法は依然として問題を引き起こします。文法にあいまいさを導入するとすぐに、パーサーは、最初に一致したものが決定的であるかどうかを確認できなくなります。あいまいさを排除するために、ステートメントを解析する他のすべての方法を試行し続ける必要があります。また、LL(1)言語のような単純なものを扱っていないため、単純で小さく高速なパーサーを使用できません。文法には複数の方法で読み取れる記号が含まれているため、多くのバックトラックを準備する必要があります。
一部の制限されたドメインでは、式の解析に考えられるすべての方法が同等であることを証明できる場合があります(たとえば、連想操作を表すため)。(a + b)+ c = a +(b + c)。
たとえば、C ++で最も厄介な解析を行います。
bar foo(foobar());
これはfoo
、型の関数宣言bar(foobar())
(パラメーターはを返す関数ポインターですfoobar
)か、foo
型の変数宣言でint
、デフォルトの初期化で初期化されていますfoobar
か?
これは、パラメーターリスト内の式を型として解釈できない場合を除き、最初のパラメーターを想定することでコンパイラーで区別されます。
このようなあいまいな式を取得すると、コンパイラには2つのオプションがあります
式が特定の派生であると想定し、他の派生を表現できるように文法に曖昧さを取り除くものを追加します。
いずれかの方法でエラーが発生し、曖昧性解消が必要
最初のものは自然に抜け落ち、2番目のものはコンパイラプログラマが曖昧さを知っていることを要求します。
このあいまいさが検出されないままの場合、2つの異なるコンパイラーがそのあいまいな式の異なる派生をデフォルトとする可能性があります。明白ではない理由により、コードが移植不能になる。これは、実際には言語仕様の欠陥である一方、コンパイラの1つのバグであると人々に思わせます。
この質問には、せいぜい正しい境界線のみであるという仮定が含まれていると思います。
実際の生活では、あいまいな文法(あいにく)があいまいでない限り、単にあいまいな文法と一緒に暮らすことはかなり一般的です。
たとえば、yacc(またはbisonやbyaccなどの類似の)でコンパイルされた文法を見てみると、コンパイル時に「N shift / reduct conflicts」に関する警告がかなりの数生成されることがわかります。yaccがshift / reduceコンフリクトに遭遇すると、それは文法のあいまいさを示します。
ただし、シフト/リデュースの競合は、通常、かなり小さな問題です。パーサージェネレーターは、reduceではなく「shift」を優先して競合を解決します。それがあなたが望むものであるなら、文法は完全に素晴らしいです(そして、それは実際に完全にうまくいくようです)。
通常、この一般的な順序のケースでは、シフト/リデュースの競合が発生します(非ターミナルにはキャップを使用し、ターミナルには小文字を使用します)。
A -> B | c
B -> a | c
に遭遇するc
と、あいまいさがあります:をc
直接解析するA
必要B
がありA
ますか、それとも解析する必要がありますか?このような場合、yaccなどはより単純な/より短いルートを選択し、-> -> ルートではなく、c
として直接解析します。これは間違っている可能性がありますが、もしそうなら、おそらく文法に非常に単純なエラーがあることを意味し、可能性としてオプションをまったく許可すべきではありません。A
c
B
A
c
A
これで、対照的に、次のようになります。
A -> B | C
B -> a | c
C -> b | c
私たちが遭遇したときに今、c
私たちは、治療するかどうかの間に矛盾していc
てB
かC
。自動競合解決戦略が本当に必要なものを選択する可能性はずっと低くなります。これらはどちらも「シフト」ではなく、どちらも「削減」であるため、これは「競合の削減/削減」です(yaccなどに慣れている人は、一般に、シフト/削減の競合よりもはるかに大きな問題として認識します)。
だから、私は誰もが文法のあいまいさを本当に歓迎していると言っているかどうかはわかりませんが、少なくともいくつかのケースでは、誰もそれについてあまり気にしていないほど十分にマイナーです。要約では、彼らはすべての曖昧さを除去するというアイデアを好むかもしれませんが、常に実際にそれを行うのに十分ではありません。たとえば、わずかなあいまいさを含む小さく単純な文法は、あいまいさを排除するより大きく複雑な文法よりも好ましい場合があります(特に、実際に文法からパーサーを生成し、曖昧でないことを見つける実用的な領域に入る場合)文法は、ターゲットマシンで実行されないパーサーを生成します)。