私はこの原則が何であるのか、そしてなぜそれが言語設計にとってそれほど重要なのかを理解するのに苦労していることに気づきました。
基本的に、言語のすべての式expr
は、この構成とまったく同じである必要があると述べています。
(function () { return expr; })()
また、私はRubyがこの原則に従っていると聞きましたが、Pythonはそうではありません。なぜこれが真であるのか、またはそれが真であるのかわかりませんまったく。
私はこれまで「テネントの対応原則」について聞いたことがなく、言語設計において重要であるということさえもありませんでした。式をグーグルすることはすべて、2006年のNeal Gafterブログの1つにつながり、彼の考えを説明し、クロージャーにもそれを適用する必要があると考えています。フォーラムの他のほとんどすべてがGafterのエントリを参照しているようです。
ただし、ここでは「T.C.P.」について言及しています。 Douglas Crockford(私が知って信頼している名前): http://Java.sys-con.com/node/793338/ 。ある程度
Tennentの対応原則(またはTCP)の主張者が悪臭の症状であると主張するreturnステートメントやbreakステートメントなど、そのように囲むことができないものがあります。うわー!言語デザインは、嗅覚の幻覚に対処する必要がない限り、すでに十分に困難です。そのため、問題をよりよく理解するために、私はTennentの1981年の本、「プログラミング言語の原則」を購入しました。
対応原理は記述的ではなく記述的であることがわかります。彼は(今では忘れられている)Pascalプログラミング言語を分析するためにそれを使用して、変数定義と手続きパラメーターの間の対応を示しています。 Tennentは、returnステートメントの対応の欠如を問題として識別しません。
したがって、「テネントの対応原則」という名前は誤用されているように思われ、ニールが語るあらゆることは、「Gafter's Imagined and possible Generalized T.C.P.」などと呼ばれるべきです。絶版本のネームカーテンの後ろに隠れるには不十分なイベント
これは、適切に設計された言語がプログラマーが自然に期待することを行うという一般的な規則の一部と見なしています。クロージャーにリファクタリングしたいコードのブロックがあり、コードの個々の行を実際に考えずに適切な構文でそのブロックをラップする場合、そのブロックはクロージャーでそれと同じことを行うと期待しますインライン化しました。一部のステートメントが「おそらく」というキーワードを使用しており(おそらく暗黙的に)、クロージャ内で使用される「this」は、クロージャを定義するメソッドを定義するクラスではなく、それを表すために使用される匿名クラスを参照する場合、その意味は次のとおりです。これらのステートメントが変更され、私のコードブロックが思ったとおりに機能しなくなったため、バグを追跡し、クロージャーで機能するようにコードを変更する方法を理解する必要があります。私がそのような問題を起こさないようにする言語は役に立ちます。
この問題は、IDEスマートリファクタリングツールを使用して軽減することもできます。これにより、クロージャを抽出し、潜在的な問題を検出し、抽出されたコードを自動的に調整して問題を解決できます。
クラウス・ラインケ: テネントの「セマンティックプリンシプルに基づく言語デザイン」について
原則の興味深い解釈を提供します:
「対応とは、
let(this=obj, x=5) { .. }
そして
((function(x) { .. }).call(obj,5))
同等である必要があります。また、仮パラメーターリストで実行できることはすべて、宣言でも実行できる必要があります(その逆も同様です)。[下記のReinkeも参照してください。]
R. D.テネント: 意味論的原理に基づく言語設計手法
"プログラミング言語セマンティクスへの表記アプローチから導出された原則に基づく2つの言語設計方法は、Pascal言語へのアプリケーションによって記述および図示されます。原則は、最初にパラメトリックメカニズムと宣言メカニズムの間の対応です。 、集合論から採用されたプログラミング言語の抽象化の原理。Pascalのいくつかの有用な拡張と一般化は、配列パラメーターの問題の解決策とモジュール化機能を含むこれらの原理を適用することによって現れます。」
クラウス・ラインケ:Haskellの「関数型プログラミング、言語設計、持続性について」
なぜTennentのCPが言語設計にとって非常に重要であるかという質問に答えるために、私は 引用Neal Gafter にしたいと思います。
Tennentの原則は非常に強力です。それらの違反は、欠陥、不規則性、不必要な制限、予期しない相互作用や合併症などとして言語に現れる傾向があるためです。
TCP=の違反は、クロージャーが非クロージャーコードのように機能することを期待しているが、TCPの違反ではそうではないことがわかった場合、将来的に一部のプログラマーを傷つける可能性があります。
RE Pythonこの原則に従っていません。一般に、この原則に従います。基本的な例:
>>> x = ['foo']
>>> x
['foo']
>>> x = (lambda: ['foo'])()
>>> x
['foo']
ただし、Pythonは式とステートメントを別々に定義します。if
ブランチなので、while
ループ、破壊的代入や他のステートメントはlambda
式ではまったく使用できません。Tennent原則の手紙はそれらに適用されません。それでも、Python式のみを使用するように制限しますチューリング完全なシステムを生成します。したがって、これを原則の違反とは見なしません。つまり、原則に違反している場合、ステートメントと式を別々に定義する言語は原則に準拠できない可能性があります。
また、lambda
式の本体がスタックトレースをキャプチャしている場合や、VMで他のイントロスペクションを行っている場合は、違いが生じる可能性があります。しかし、私の意見では、これは違反として数えるべきではありません。 expr
と(lambda: expr)()
必要に応じて同じバイトコードにコンパイルすると、原則はセマンティクスではなくコンパイラに関係します。しかし、それらが異なるバイトコードにコンパイルできる場合、VMの状態がいずれの場合も同じであることを期待するべきではありません。
理解の構文を使用すると、驚きに遭遇する可能性がありますが、これはTennentの原則の違反でもないと私は思います。例:
>>> [x for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [lambda: x for x in xrange(10)]] # surprise!
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
>>> # application of Tennent principle to first expression
... [(lambda: x)() for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [(lambda x: lambda: x)(x) for x in xrange(10)]] # force-rebind x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> map(lambda f:f(), map(lambda x: lambda: x, xrange(10))) # no issue with this form
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
驚いたのは、リスト内包表記の定義の結果です。上記の「驚き」の理解はこのコードと同等です:
>>> result = []
>>> for x in xrange(10):
... # the same, mutable, variable x is used each time
... result.append(lambda: x)
...
>>> r2 = []
>>> for f in result:
... r2.append(f())
...
>>> r2
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
このように見ると、上記の「驚き」の理解はそれほど驚くべきものではなく、テネントの原則に違反するものではありません。