Edited at
D言語Day 2

2種類のmixinの違い

More than 3 years have passed since last update.

D言語 Advent Calendar 2014の2日目の記事です。

更に、Aizu Advent Calendar 2014の2日目の記事でもあります。

D言語にはテンプレートmixinと文字列mixin(宣言)があります。文字通り、テンプレートmixinはテンプレートの中身を別の場所に展開する機能、文字列mixinは文字列の中身をソースコードとして展開する機能です。どちらもコード片をソースコードとして展開する機能を持っていますが、それぞれ利点、欠点が異なるので、それらの中で個人的に思い入れの強いものを挙げていきます。その上で、実際にmixinを使いたいときにどういう選択をするべきかを考えます。


TL;DR

テンプレートmixinで済むときはテンプレートmixinを使う。そうでない時は文字列mixinをテンプレートmixinでラップする。


テンプレートmixin


利点:mixinされる前に構文チェックしてくれる

テンプレートmixinは、コード片としてテンプレートを書きます。よって、mixinされる前にコンパイラが構文チェックを行ってくれます。

import std.stdio : writeln;

mixin template Singleton()
{
private static typeof(this) instance // セミコロンが無いので、構文エラーが起きる

static typeof(this) get()
{
if (!instance)
{
instance = new typeof(this)();
}

return instance;
}
}

気をつけて欲しいのは、実際にmixinされるまで意味チェックは行われません。この特徴は通常のテンプレートと同じです。


利点?:mixinするテンプレートをかっこで囲まない

構文の良し悪しの話です。個人的には利点です。

class A

{
mixin Singleton; // テンプレートに渡す引数が無いときは、かっこが不要
}

mixin HogeTemplate!arg1; // 引数が1つの時も、かっこは不要

mixin PiyoTemplate!(arg1, arg2); // 引数が2つの時は、引数リストのかっこが必要

どの場合にしても、FugaTemplate!(...)自体をかっこで囲みません。


欠点:宣言しかmixin出来ない

個人的に、テンプレートmixin最大の欠点だと思ってます。そもそも、テンプレート内には宣言しか書けません。言い換えると、テンプレート内には文や式を書くことが出来ません。よって、テンプレートmixinでmixinできる内容は宣言のみとなります。

mixin template w(string str)

{
static import std.stdio;
std.stdio.writeln(str); // 構文エラー
}

が、この制限を回避することのできる方法があります。D言語の宣言の中に、ひとつだけ、その宣言が処理される際に式を実行するものがあります。それが変数宣言です。式が実行できるのであれば、文を実行するのは簡単です。

mixin template w(string str)

{
int YOU_SHOULD_NOT_REFER_ME = function()
{
static import std.stdio;
std.stdio.writeln(str);
return 0;
}(); // 無名関数を作成した直後に呼び出す
}

void main()
{
mixin w!"hoge";
}

当然、この方法には名前空間を汚すという欠点があります。YOU_SHOULD_NOT_REFER_MEなんて名前を使う人がいるとは思えませんが、可能性は残ります。


文字列mixin


利点:宣言、文、式をmixinできる

上にも書きましたが、文字列mixinは宣言、文、式のそれぞれに存在します。よって、3種類すべてmixinすることが可能です。

string importStd(string package_)

{
return "import std." ~ package_ ~ ";";
}

string callWriteln(string content)
{
return "writeln(" ~ content ~ ");";
}

void main()
{
auto ary = mixin ("[1, 2, 3]"); // 式
mixin (importStd("stdio")); // 宣言

mixin (callWriteln("ary")); // 文
}


欠点:mixinされる前に構文チェックが行われない

文字列mixinでは文字列をmixinするので、当然と言えば当然です。

string callWriteln(string content)

{
return "writeln(" ~ content ~ ")"; // writeln(...)のあとに;が無いが、実際にmixinされるまで構文エラーが出ない
}


欠点?:mixinするものをかっこで囲む必要がある

テンプレートmixinのようにかっこで囲まない、ということはことは出来ません。個人的には欠点です。

void main()

{
mixin importStd("stdio"); // 構文エラー
}


まとめ

テンプレートmixin
文字列mixin

mixinできるもの
宣言のみ
宣言、文、式

構文チェック
mixinされる前
mixinされた後

mixinするものを囲むかっこ
不要
必要

上記以外にも、表現力や構文エラーの分かりやすさが違います。


どちらが良いか

宣言をmixinする場合で、かつテンプレートmixinの表現力で十分な場合、テンプレートmixinを使うのが良いと思います。そうでない場合も、文字列mixinをテンプレートmixinでラップし、一番外側のインターフェースをテンプレートmixinにするのが良いと思います。

式や文をmixinする時は、甘んじて文字列mixinを使うのか、名前空間を汚す覚悟を持って上記の回避策を使うのか、どちらがいいかはよく分かりません。ただ、名前空間を汚す場合、ドキュメントなどに使用した変数名を書くとハマる人が減るのではないでしょうか。

Aizu Advent Calendar 2014 -> @yutopp

D言語 Advent Calendar 2014 -> @mono_shoo