必要に応じて原子を次々に配置できたらどうでしょうか?
リチャード・ファインマン
プログラミングパラダイムはいくつ命名できますか? このウィキペディアページのリストには、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;
利点は明らかです:テンプレートの効率、適切な分離、コードの再利用。 ただし、コンポーネントは固体であり、分離できません。 インターフェイスの一部が欠落している場合、コンパイルエラーが発生します。 コンポーネントに「可塑性」を与えるために、このスキームを開発してみましょう。
必要条件
したがって、これには次のものが必要です。
- タイプの内省:「どのメソッドがありますか?」、「xyzメソッドをサポートしていますか?」
- コンパイル時のコード実行
- コード生成
各ポイントに使用できるD言語ツールを見てみましょう。
-
.tupleof
、__traits
、std.traits
__traits
は、コンパイラに組み込まれたリフレクションツールです。std.traits
は組み込み特性のライブラリ拡張であり、その中でhasMember
関数に関心があります。 - CTFE 、
static if
、static foreach
コンパイル中に、大規模なクラスの関数(実際には、移植性があり、グローバルな副作用のない関数)を実行できます。
static if
、static foreach
はコンパイル時のif
およびforeach
。 - テンプレートとミックスイン
D言語のミックスインには、テンプレートと文字列の2つの形式があります。 前者は、プログラム内のある場所に一連の定義(関数、クラスなど)を挿入するために使用されます。 後者は、コンパイル時に生成された文字列を直接コードに変換します。 通常、ストリングミックスインは小さな部分で使用されます。
オプションのインターフェース
Design by Introspectionの最も重要な機能は、オプションのインターフェイスです。 ここで、コンポーネントにはR必須プリミティブ(おそらく0)とOオプションが含まれています。 イントロスペクションの助けを借りて、特定のプリミティブが与えられているかどうかを調べることができ、欠落しているプリミティブに関する知識は、コンポーネントに含まれているプリミティブと同じくらい重要です。 したがって、可能なインターフェースの数は2 Oになります。
static if
「マジックフォーク」を作成するシンプルで強力なツール。コードを使用するためのオプションの数を2倍にします。 これにより、可能な動作の数が指数関数的に増加する線形コードを作成できます。 コンパイラーによって生成されるコードの指数関数的な増加は発生しません。アプリケーションで実際に使用するテンプレートのインスタンスに対してのみ料金を支払います。
例
DbIの使用例として、整数を使用した安全な作業を実装する標準Phobosライブラリのモジュールであるstd.experimental.checkedintを検討してください 。 どのマシン整数演算が安全ではありませんか?
- +、+ =、-、-=、++、-、、 =はオーバーフローを引き起こす可能性があります
- /および/ =のゼロ除算
- -x.minは、署名された型の場合、それ自体と等しい
- -1 == uint.max、-1> 2u
- ...
各操作の後に正直にチェックを挿入するか、これを行うタイプを開発することができます。 これは多くの質問を提起します:
- どのタイプをチェックする必要があります
- 検証に違反した場合の対処方法
- 禁止する操作/変換
- すべてを効果的にする方法
- 最後に、ライブラリを使用するための20ページのマニュアルがライブラリに付属しないように、それを単純にする方法
基本型と「フック」をテンプレートパラメータとして受け入れる「シェル」を作成して、チェックを実行します。
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
モジュールは、いくつかの標準フックを定義しています。
- 中止:
assert(0)
- 投げる:例外を投げる
- 警告:stderrに警告を表示します
- ProperCompare:正しい比較を行います
- WithNaN:何かが間違っていることを示すいくつかのNaN値に置き換えます
- 飽和:最小値と最大値を超えないでください
フックには次のものが含まれます。
- 静的フィールド:defaultValue、min、max
- 演算子:hookOpCast、hookOpEquals、hookOpCmp、hookOpUnary、hookOpBinary、hookOpBinaryRight、hookOpOpAssign
- イベントハンドラー:onBadCast、onOverflow、onLowerBound、onUpperBound
また、独自のコードを作成するのに必要なコードは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)) {...}
これは興味深い可能性を開きます:
-
Checked!(Checked!(int, ProperCompare))
:比較を修復し、他の状況でクラッシュする -
Checked!(Checked!(int, ProperCompare), WithNaN)
:比較を修復し、他の状況では「NaN」を返します
また、無意味な組み合わせを作成します。
- 中止、スロー、警告には互換性がありません
- 中止/適切な前に投げる/ WithNaN /飽和する
そしてただ奇妙な:
- 摩耗を発行してから修正する
- 最初に修正し、次に渦を出す
- など
この問題を解決するには、「半自動」構成を使用することをお勧めします。
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は開発者ツールのサポートを必要とします。