LoginSignup
1
1

More than 5 years have passed since last update.

D言語でも無名クラスが使いたい!

Last updated at Posted at 2016-03-30

初めに

どうもこんにちは

今日はD言語でJavaのような無名クラスが使いたいなぁって思って書いてみたものについて書こうと思います。
ただし、僕はJavaについては全く知りませんので間違ったことを言っているかもしれません。(Scalaを最近学習中でその時にちょっとだけJavaを書いた程度です...)

追記

僕が知らなかっただけで、D言語の標準機能にも無名クラスがありました...(Gistのコメントで教えてもらいました...)
でもとりあえず、書いちゃったものについて書きます...
D言語でコンパイル時文字列操作をする際の参考になれば、と思ってかきます

さて、

まずここで実装するものをJavaで書いてみます。
Javaでは1Class 1Fileというのが原則らしいのですが、今回は2つのClassを書きます(なのでツッコミは無しで)。

class T1 {
  T1(String v) {
    System.out.println("T1! - " + v);
  }

  void func() {}
}

class Main {
  static public void main(String[] args) {
    T1 t = new T1("T2") {
      @Override void func() {
        System.out.println("Overrided T2.func!");
      }
    };
    t.func();
  }
}

これを実行すると

T1! - T2
Overrided T2.func!

と出力されます

ここでいう匿名クラスとは、newでコンストラクタを読んだ時にoverrideするためのブロックについてです。

コレをD言語でやってみようというのが今回の目的です

どうやってD言語でやるか

コレをD言語で実装するには、コンパイル時にテンプレートを用いて文字列を生成しそれをmixinする、テンプレートメタプログラミングを用います。
D言語ではmixinというものを用いることでコード中の文字列リテラルをD言語のコードとして解釈しそれをその場に埋め込む、展開する機能です。
つまり

int x;
mixin("x = 1 + 2;");
assert(x == 3);

となります。

コレをどうつかって実装するか、ですが
方針としては

  • オーバーライドしたいメソッドを持つClassを基底クラスAとする
  • 基底クラスAを継承したクラスBを生成する
  • クラスBの中にoverrideした関数を埋め込む

これをすべて文字列の中で行って、完成したものをmixinする方針です。

つまり

mixin("class T {}");
void main() {
  T t = new T;
}

みたいな感じのことをします。

課題

これ、一見すると簡単です

bodyStringにオーバーライドするコードを入っているとして

string mixinString =
  "class T2 : T1 {"
~    bodyString
~  "}";

このように実装すればいいような気がします。
これなら単純です。
しかし、事はそう単純ではありません。
感の良い方は気がついたかもしれませんが、そうです。superを呼ぶコンストラクタが必要です。

それで、superが必要であることによって発生する課題は
superの引数をどう渡すか、ということです。

ちなみに、
今回は、都合により継承したクラスの方のsuperを呼ぶコンストラクタは、superを呼ぶだけ、にします。
つまり、

this() {
  super();
}
//or
this (ArgType1 value1, ArgType2 value2) {
  super(value1, value2);
}

といった感じです。

ここで、引数はstd.typecons.Tupleを用いて渡すことにします。

とりあえず目指す先を示しておきましょう
T1というクラスのfuncという関数をoverrideしたいものとします

では、T1について

class T1 {
  this(string v) {
    writeln("T1 - ", v);
  }

  void func() {}
}

このように定義します
ここで、これを継承したT2を生成することを目指します。
完成したT2を示します

class T2 : T1 {
  this(string v) {
    super(v);
  }

  override void func() {
    writeln("Overrided T2.func!");
  }
}

こんなかんじのものを作ります。

実装した後のものを使うとこのような感じになります

T1 t = AnonymousClass!("T1", q{
        override void func() {
          writeln("Overrided T2.func!");
        }
      }, tuple("T2"));

ここで、"T1"が基底クラス名BaseClassName, override〜の文字列を実装本体のbodyString, tuple("T2)をコンストラクタの引数argumentsとします。

ここでargumentsを用いて以下の文字列を作れば引数どうするの問題を解決できます

  • types : コンストラクタの引数の型の配列(例えば["int", "int"]みたいなもの)
  • argumentLabels : 引数の名前の配列
  • argumentValues : 引数の実引数じ文字列(例えば、(1,2)みたいなもの)
  • parameters : 関数宣言の引数リストの文字列(例えば(int x, int y)みたいなもの)

typesを作る

immutable types = (temp) {
        int i;
        foreach (argument; arguments.Types) {
          temp ~= (i ? "," : "") ~ argument.stringof;
          i++;
        }
        return temp;
      }("").split(",");

argumentLabelsを作る

immutable argumentLabels = arguments.length.iota.map!(i => " arg" ~ i.to!string).array;

argumentValuesを作る

immutable argumentValues = arguments.stringof.replace("Tuple", "");

parametersを作る

immutable parameters =
  (temp =>
    (Args) {
      int i;
      foreach (argument; arguments) {
        temp ~= (i ? "," : "") ~ types[i].to!string ~ Args[i];
        ++i;
      }
      return temp;
    }(argumentLabels)
  )("");

ここまで変数を用いて以下の文字列を生成します

  "() {"
~   "class AnonymousClassMain : " ~ BaseClassName ~ "{"
~     "this(" ~ parameters ~ ") {"
~       "super(" ~ argumentLabels ~ ");"
~     "}"
~     bodyString
~   "}"
~   "return new AnonymousClassMain(" ~ argumentValues ~ ");"
~ "}()";

ここで、無名関数にしてるのは、後にmixinする際に式の値としてmixinするので、一つの値とするためです。

完成品はこちらになります

以上をまとめて、コンストラクタの引数がない場合は3つめのargumentsを省略可能に出来るようにしたものが以下のコードになります

import std.algorithm,
       std.typecons,
       std.string,
       std.array,
       std.range,
       std.conv;

template AnonymousClassImpl(
    string BaseClassName,
    string bodyString,
    alias arguments = tuple()
  ) {
  enum AnonymousClassImpl = () {

    string generateString(
          string parameters      = null,
          string argumentLabels  = null,
          string argumentValues  = null
        ) {
      if (
          parameters     !is null
       && argumentLabels !is null
       && argumentValues !is null) {
        return 
          "() {"
        ~   "class AnonymousClassMain : " ~ BaseClassName ~ "{"
        ~     "this(" ~ parameters ~ ") {"
        ~       "super(" ~ argumentLabels ~ ");"
        ~     "}"
        ~     bodyString
        ~   "}"
        ~   "return new AnonymousClassMain(" ~ argumentValues ~ ");"
        ~ "}()";
      } else {
        return 
          "() {"
        ~   "class AnonymousClassMain : " ~ BaseClassName ~ "{"
        ~     "this() {"
        ~       "super();"
        ~     "}"
        ~     bodyString
        ~   "}"
        ~   "return new AnonymousClassMain();"
        ~ "}()";
      }
    }

    static if (arguments.length == 0) {
      return generateString;
    } else {
      immutable types = (temp) {
        int i;
        foreach (argument; arguments.Types) {
          temp ~= (i ? "," : "") ~ argument.stringof;
          i++;
        }
        return temp;
      }("").split(",");
      immutable argumentLabels = arguments.length.iota.map!(i => " arg" ~ i.to!string).array;
      immutable argumentValues = arguments.stringof.replace("Tuple", "");
      immutable parameters =
        (temp =>
          (Args) {
            int i;
            foreach (argument; arguments) {
              temp ~= (i ? "," : "") ~ types[i].to!string ~ Args[i];
              ++i;
            }
            return temp;
          }(argumentLabels)
        )("");

      return generateString(
            parameters,
            argumentLabels.join(","),
            argumentValues
          );
    }
  }();
}

auto AnonymousClass(
      string BaseClassName,
      string bodyString,
      alias arguments = tuple()
    )() {
  static if (arguments.length == 0) {
    return mixin(AnonymousClassImpl!(BaseClassName, bodyString));
  } else {
    return mixin(AnonymousClassImpl!(BaseClassName, bodyString, arguments));
  }
}

import std.stdio;

class T1 {
  this(string v) {
    writeln("T1! - ", v);
  }

  void func() {}
}

class Tx1 {
  this() {
    writeln("Tx1");
  }
  void func() {}
}

void main() {
  T1 t = AnonymousClass!("T1", q{
        override void func() {
          writeln("Overrided T2.func!");
        }
      },
      tuple("T2"));
  Tx1 tx = AnonymousClass!("Tx1", q{
      override void func() {
          writeln("Overrided Tx2.func!");
        }
      });
  t.func;
  tx.func;
}

Dにも無名クラスがあったという話

コレを昨夜実装して、今朝起きてこの記事を書き始めた時に、上のコードを載せたGistを見に行ったらコメントでDにも無名クラスがあることを教えてもらいました...
上のコードは下のコードと等価です(内部の実装などは異なりますが同じ挙動をします...)

import std.stdio;

class T1 {
  this(string v) {
    writeln("T1! - ", v);
  }

  void func() {}
}

class Tx1 {
  this() {
    writeln("Tx1");
  }

  void func() {}
}

void main() {
  T1 t = new class() T1 {
    this() {
      super("T2");
    }

    override void func() {
      writeln("Overrided T2.func!");
    }
  };

  Tx1 tx = new class() Tx1 {
    this() {
      super();
    }

    override void func() {
      writeln("Overrided Tx2.func!");
    }
  };

  t.func;
  tx.func;
}

はい、コレを使えばD言語の標準機能で無名クラスが使えますね(涙) (他にも自分でコンストラクタが書けるのでその分の自由度は高いですね...(コンストラクタのコードを自分で書けるようにも出来ますが、その分引数が増えてしまって面倒になるのでコンストラクタに制限を加えました))
でもまぁ、D言語の黒魔術(コンパイル時文字列操作)の勉強になったのでいいとしよう...

とりあえず、以上です

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1