D言語 Advent Calendar 2013の9日目の記事です.
最近のD言語は,破壊的更新は減ってきたものの,eponymous templateやテンプレート関数と非テンプレート関数のオーバロードなど,魅力的な新機能の導入や,過去にあった制限の緩和が多くなされています.
しかし標準ライブラリの Phobos は,それらの変更に完全に追従できているわけではありません.
今回は,まだ Phobos が完全には追従できていないD言語の機能と,(大まかな)修正方法を紹介します.
簡単そうなものから並べていますので,上の方から皆で Pull Request を投げましょう!どんどん投げましょう!
Documented Unittest (2.063)
以下のように関数やメソッドの下にある unittest ブロックに ///
とコメントを付けると,Ddoc は
その unittest ブロックの内容を,関数の実行例としてドキュメントに出力してくれます.
/// sum function
int sum(int x, int y) { return x + y; }
///
unittest
{
assert(sum(2, 2) == 4);
}
この機能への追従方法は,Phobos の関数のコメント中にある実行例を unittest ブロックに書き換え,
documented unittest 化すればOKです.
注意点としては,
- (documented unittest 化されていないものの) unittest ブロック内にコメントの実行例と同じものがある場合,その部分だけ documented unittest に変更するのがいいでしょう.
- 定義順として,
- 関数本体
- documented unittest
- 普通の unittest
のようにしておくとマージされやすくなると思います.
New alias syntax (2.062)
alias int newtype;
の代わりに
alias newtype = int;
のように書けます.修正方法もそのままです.
今気づきましたが,公式のドキュメントもまだ追従できていないようです.
Eponymous template (2.064)
既存の関数テンプレートやクラステンプレートの記法を enum 等に拡張したものです.
template isIntOrFloat(T)
{
static if (is(T == int) || is(T == float))
enum isIntOrFloat = true;
else
enum isIntOrFloat = false;
}
の代わりに
enum isIntOrFloat(T) = is(T == int) || is(T == float);
のように書けます.
これとCTFE (コンパイル時関数実行)を組み合わせると,
理系擬態系文系男子の日常を: コンパイル時FizzBuzzの最後のコードような素敵な感じになります.
例えば std.range 等のモジュールでは,isInputRange
等の Range 用のテンプレートが数多く定義されている
(& eponymous template化されていない)ため,数が多いですがそれぞれは修正しやすいと思います.
safe/trusted/system, pure, nothrow 属性 (2.021くらい?)
各属性はそれぞれ役割が違いますが,難易度的には大体同じくらいと思っているのでここでまとめて扱います.
難易度としては,簡単なものから難攻不落の要塞まで様々です(やってみた経験上).
safe/trusted/system
これらはメモリ安全性に関する属性で,例えば
@safe auto foo() { ... }
のような @safe
属性がついた関数は,関数内で可能な操作が制限される代わりに,バッファオーバーラン等をはじめとした未定義動作を引き起こさないことがコンパイラによって静的に検証されます.
@trusted
属性は関数を定義したプログラマが未定義動作を起こさないことを保証することを示し,@system
属性は,引数等によっては未定義動作を引き起こす可能性のあることを示しています (std.c にある関数は大体これです).
参考:
pure
関数がグローバル変数の操作や入出力を行わない関数であることを示す属性です.
pure 関数内での変数の書き換え等が可能な点が,他の言語での(いわゆる)純粋関数との大きな違いになると思います.
出力が引数のみによって決まるため,デバッグがしやすくなる他,
この属性が付いている関数の返り値は immutable に暗黙変換できる場合がある等の面白い性質があり,今後のD言語的に重要になりそうな属性です.
参考:
nothrow
関数が例外を投げないことをコンパイラが保証することを示す属性です.
基本的な修正方法
注意: @safe
等の属性を付けた後は,ちゃんとユニットテストを完走するかどうか確認しましょう!
safe/trusted/system
- 引数に
char*
等がある場合,ほとんどの場合は system 関数なので@system
を付けましょう. - 「これは明らかにメモリ破壊起こさない関数だろう常識的に考えて」みたいなものは
@safe
を付けるべきですが,
内部で system 関数等を使っている場合があります.この場合,関数に@trusted
を付けるか,
safe にできない原因となっている箇所のみを,以下のように関数内 trusted 関数として定義して,元の関数を safe 化する方法があります.
@safe auto foo()
{
// ...safe な処理
auto unsafeButTrusted() @trusted {
// unsafe だけど tursted として保証できる処理
}
auto result = unsafeButTrusted();
// ...safe な処理
}
- 関数内関数の代わりに trusted ラムダ式を使う方法は,dmd の Issue 10848がマージされるまでは実行速度の関係で使わないほうがいいでしょう.
pure
- 「これは明らかに引数のみに依存する関数だろう常識的に考えて」みたいなものは pure であるべきなので
pure
を付けましょう.
現状 trustedPure のような属性はないため,内部で impure (だけど pure であるべき)関数が使われている場合には,
修正は難しいかもしれません.
nothrow
- 例外を投げない関数に
nothrow
を付ければいいのですが,後で例外を投げるような修正を加えることができなくなり,後方互換性を壊してしまう場合があるため(!),これの修正は慎重になったほうがいいでしょう.特に,ほとんどの文字列操作は例外を投げる可能性があるようなので注意しましょう.
修正が厄介な場合
テンプレート関数の場合,これらの属性に関する修正が難しくなります.というのも,
- テンプレート引数に何が来るかによって safe,pure,nothrow 等の属性が変わるため,関数に直接これらの属性を付けることができない.
- D言語はテンプレート引数からこれらの属性を自動推論してくれますが,テンプレート引数によらずsafe,pure,nothrowである場合があり,これらの判断が難しい.
- ある特定のテンプレート引数の場合は safe であって欲しい場合がある.例えば,
std.stdio.writeln
のようなテンプレート関数は,int
等の組み込み型に対してはsafe関数であるべきです. - unittest ブロック等でテンプレートの実体化を行わないと,safe,pure,nothrowの確認ができない(実体化されないテンプレートはチェックされない).
攻略の糸口
以下の方法が遠回りですが確実だと思います.
- 簡単なテンプレート関数のうち,テンプレート引数によらずsafe,pure,nothrow が決まるものについては属性を付ける.これによって,大規模なテンプレート関数内で属性を付けられる or 付けられないかどうかを判断する箇所が減ります.
- 関数内部で trusted にできる部分 (system 関数を使っているが引数等から見て safe だと判断できる場合)を積極的に関数内trusted関数にする.
- 気合
まとめ
最後の属性以外は結構簡単なのでみんなで Pull Request 投げましょう!どんどん投げましょう!
おまけ
今年は時差なしで記事書けたよ!大勝利!