イントロスペクションによる設計

必要に応じて原子を次々に配置できたらどうでしょうか?
リチャード・ファインマン

プログラミングパラダイムはいくつ命名できますか? このウィキペディアページのリストには、76個ものアイテムが含まれています。 このリストは、Introspectionによる設計と呼ばれる別のアプローチによって拡張できます。 彼の主なアイデアは、単純なメタプログラミングとイントロスペクションタイプ(コンパイル時間)を積極的に使用して、弾性コンポーネントを作成することです。


このアプローチの作者はAndrei Alexandrescuです。 この記事では、DConf 2017でのスピーチの資料を使用しました。


背景


2001年に、 ポリシーベースデザインと呼ばれる本が、C ++のモダンデザインの本に紹介されました。 一般に、これは「戦略」パターンですが、パターンを使用してコンパイル時にコンパイルします。 ホストテンプレートクラスは、各種類の独立した機能を実装するポリシータイプのセットをパラメーターとして受け入れ、これらのコンポーネントによって提供されるインターフェイスを内部的に使用します。


struct Widget(T, Prod, Error) { private T frob; private Prod producer; private Error errPolicy; void doWork() { //    //  duck-typing } } 

構文の説明

ここでは、テンプレートは短い構文で説明されています。 (T, Prod, Error) -そのパラメーター。
インスタンス化は次のようになります。


 Widget!(int, SomeProducer, SomeErrorPolicy) w; 

利点は明らかです:テンプレートの効率、適切な分離、コードの再利用。 ただし、コンポーネントは固体であり、分離できません。 インターフェイスの一部が欠落している場合、コンパイルエラーが発生します。 コンポーネントに「可塑性」を与えるために、このスキームを開発してみましょう。


必要条件


したがって、これには次のものが必要です。


  1. タイプの内省:「どのメソッドがありますか?」、「xyzメソッドをサポートしていますか?」
  2. コンパイル時のコード実行
  3. コード生成

各ポイントに使用できるD言語ツールを見てみましょう。


  1. .tupleof__traitsstd.traits
    __traitsは、コンパイラに組み込まれたリフレクションツールです。 std.traitsは組み込み特性のライブラリ拡張であり、その中でhasMember関数に関心があります。
  2. CTFEstatic ifstatic foreach
    コンパイル中に、大規模なクラスの関数(実際には、移植性があり、グローバルな副作用のない関数)を実行できます。
    static ifstatic foreachはコンパイル時のifおよびforeach
  3. テンプレートとミックスイン
    D言語のミックスインには、テンプレートと文字列の2つの形式があります。 前者は、プログラム内のある場所に一連の定義(関数、クラスなど)を挿入するために使用されます。 後者は、コンパイル時に生成された文字列を直接コードに変換します。 通常、ストリングミックスインは小さな部分で使用されます。

オプションのインターフェース


Design by Introspectionの最も重要な機能は、オプションのインターフェイスです。 ここで、コンポーネントにはR必須プリミティブ(おそらく0)とOオプションが含まれています。 イントロスペクションの助けを借りて、特定のプリミティブが与えられているかどうかを調べることができ、欠落しているプリミティブに関する知識は、コンポーネントに含まれているプリミティブと同じくらい重要です。 したがって、可能なインターフェースの数は2 Oになります


static if 「マジックフォーク」を作成するシンプルで強力なツール。コードを使用するためのオプションの数を2倍にします。 これにより、可能な動作の数が指数関数的に増加する線形コードを作成できます。 コンパイラーによって生成されるコードの指数関数的な増加は発生しません。アプリケーションで実際に使用するテンプレートのインスタンスに対してのみ料金を支払います。



DbIの使用例として、整数を使用した安全な作業を実装する標準Phobosライブラリのモジュールであるstd.experimental.checkedintを検討してください 。 どのマシン整数演算が安全ではありませんか?



各操作の後に正直にチェックを挿入するか、これを行うタイプを開発することができます。 これは多くの質問を提起します:



基本型と「フック」をテンプレートパラメータとして受け入れる「シェル」を作成して、チェックを実行します。


 static Checked(T, Hook = Abort) if (isIntegral!T) // Abort   { private T payload; Hook hook; ... } 

フックには常に状態があるとは限りません。 static ifを使用してこれを考慮に入れましょう:


 struct Checked(T, Hook = Abort) if (isIntegral!T) { private T payload; static if (stateSize!Hook > 0) Hook hook; else alias hook = Hook; ... } 

ここでは、D構文では、ドットを使用して、オブジェクトのフィールドに直接、およびポインターとその静的メンバーを介してアクセスします。
デフォルト値も設定します。 これは、何らかのNaN値を定義するフックに役立ちます。 ここでは、 hasMemberテンプレートを使用します。


 struct Checked(T, Hook = Abort) if (isIntegral!T) { static if (hasMember!(Hook, "defaultValue")) private T payload = Hook.defaultValue!T; else private T payload; static if (stateSize!Hook > 0) Hook hook; else alias hook = Hook; ... } 

小さなコードに含めることができる動作の数の例として、オーバーロードされたインクリメント演算子とデクリメント演算子を引用します。


機能コード全体
 ref Checked opUnary(string op)() return if (op == "++" || op == "--") { static if (hasMember!(Hook, "hookOpUnary")) hook.hookOpUnary!op(payload); else static if (hasMember!(Hook, "onOverflow")) { static if (op == "++") { if (payload == max.payload) payload = hook.onOverflow!"++"(payload); else ++payload; } else { if (payload == min.payload) payload = hook.onOverflow!"--"(payload); else --payload; } } else mixin(op ~ "payload;"); return this; } 

フックがこれらの操作をインターセプトする場合、それらに委任します。


 static if (hasMember!(Hook, "hookOpUnary")) hook.hookOpUnary!op(payload); 

それ以外の場合、オーバーフローを処理します。


 else static if (hasMember!(Hook, "onOverflow")) { static if (op == "++") { if (payload == max.payload) payload = hook.onOverflow!"++"(payload); else ++payload; } else { // --  } } 

最後に、何もインターセプトされなかった場合、通常どおり操作を使用します。


 else mixin(op ~ "payload;"); 

このストリングミックスインは、 ++payload;展開され++payload; または--payload; 操作に応じて。


従来、インターフェイスの一部が存在しないとエラーが発生します。 ただし、ここでは、これによりいくつかの可能性がなくなります。


 Checked!(int, void) x; // x  ,   int 

std.experimental.checkedintモジュールは、いくつかの標準フックを定義しています。



フックには次のものが含まれます。



また、独自のコードを作成するのに必要なコードは50行未満です。 たとえば、符号付き数値と符号なし数値のすべての比較を禁止します。


 struct NoPeskyCmpsEver { static int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) { static if (lhs.min < 0 && rhs.min >= 0 && lhs.max < rhs.max || rhs.min < 0 && lhs.min >= 0 && rhs.max < lhs.max) { // ,     static assert(0, "Mixed-sign comparison of " ~ Lhs.stringof ~ " and " ~ Rhs.stringof ~ " disallowed. Cast one of the operands."); } } return (lhs > rhs) - (lhs < rhs); } alias MyInt = Checked!(int, NoPeskyCmpsEver); 

構成


これに先立ち、Checkedはメインパラメータとして基本タイプのみを受け入れました。 構成を提供し、別のチェック済みを受け入れます:


 struct Checked(T, Hook = Abort) if (isIntegral!T || is(T == Checked!(U, H), U, H)) {...} 

これは興味深い可能性を開きます:



また、無意味な組み合わせを作成します。



そしてただ奇妙な:



この問題を解決するには、「半自動」構成を使用することをお勧めします。


 struct MyHook { alias onBadCast = Abort.onBadCast, onLowerBound = Saturate.onLowerBound, onUpperBound = Saturate.onUpperBound, onOverflow = Saturate.onOverflow, hookOpEquals = Abort.hookOpEquals, hookOpCmp = Abort.hookOpCmp; } alias MyInt = Checked!(int, MyHook); 

aliasを使用して、既存のフックから静的メソッドを選択し、それらから独自の新しいフックを作成しました。 それが私たちが好きなように原子を配置する方法です!


おわりに


このアプローチは、 static ifが主な原因static if 。 この演算子は、コードのユースケースのスペースを拡張します。 スケーリングする場合、Design by Introspectionは開発者ツールのサポートを必要とします。



Source: https://habr.com/ru/post/J350844/


All Articles