DSL / Literal Extension


Literal Extension

一言要約

プログラムのリテラルにメソッドを追加

要約

仕組み

C#の拡張メソッドやRubyのオープンクラスといったテクニックを使って、組み込み型のリテラルを拡張する。
これにより、メソッドチェーンをリテラルから始めることができるので、DSLにとってはとても便利。

エクスプレッションビルダーを使用しない場合は、中間に位置する全ての型が適切な流れるようなメソッドを持つよう努めるべき。

キログラムを基準にした場合 42.grams の結果が0.042になり、整数が浮動小数点に変化することになる。(奇妙な変形問題)
チェーンの先のほうのすべてのメソッドは複数の型で定義される必要がある。

数量オブジェクトは以下のような場合に警告を出すなど便利な振る舞いを持たせられる。

42.grams + 35.cm

(これ以降、ほとんど意味不明。。。)

ファウラー自身はエクスプレッションビルダを好む傾向にある。

使いどころ

流れるようなインターフェイス賛同者に人気。

リテラルのインターフェイス膨張という懸念がある。
有用性と複雑さを追加する問題点とのトレードオフがある。
いくつかの言語環境はこの問題を解決するのに名前空間との結合を利用している。
(たとえばC#では拡張メソッドを使うのに、それが定義されているクラスの名前空間の使用を宣言する必要がある)


例:レシピ材料(C#)

var ingredient = 42.Grams().Of("Flour");

まずはint型にGramsメソッドを追加。

namespace dslOrcas.literalExtension {
  public static class RecipeExtensions {
    public static Quantity Grams(this int arg) {
      return new Quantity(arg, Unit.G);
    }

  public struct Quantity {
    private double amount;
    private Unit units;
    public Quantity(double amount, Unit units) {
      this.amount = amount;
      this.units = units;
    }
 
  public struct Unit {
    public static readonly Unit G = new Unit("g");
    public String name;
    private Unit(string name) {
      this.name = name;
    }
 

数量クラスが汎用的なライブラリの一部として使用される一方、Ofメソッドは限定的な目的のDSLの一部であることから、Of メソッドがクラスに属するとは考えていない。
そこで再度拡張メソッドを使用する。

    public static Ingredient Of(this Quantity arg, string substanceName) {
      return new Ingredient(arg, SubstanceRegistry.Obtain(substanceName));
    } 

DSLコードは材料オブジェクトを生成する。

  public struct Ingredient {
    Quantity amount;
    Substance substance;
    public Ingredient(Quantity amount, Substance substance) {
      this.amount = amount;
      this.substance = substance;
    }

  public struct Substance {
    private readonly string name;
    public Substance(string name) {
      this.name = name;
    }

シンボルテーブルのようにふるまうレジストリで使用するために、DSL内で材料の名前として文字列を使用。

    private static SubstanceRegistry instance = new SubstanceRegistry();
    public static void Initialize() { instance = new SubstanceRegistry(); }
    private readonly Dictionary<string, Substance> values = new Dictionary<string, Substance>();
    public static Substance Obtain(string name) {
      if (!instance.values.ContainsKey(name))
        instance.values[name] = new Substance(name);
      return instance.values[name];
    }

ファウラーへのフィードバック

担当者のつぶやき

C#の場合は拡張メソッドがあるのでいい感じに書けますよね!

みんなの突っ込み