この記事について
個人的に僕がD言語でコードを書いている時に、おっ!これ便利やん!
みたいになったD言語の機能やコーディングスタイル, ツールについて書きます。
※ あくまでも僕の個人的な意見や考え方です。
また、誤謬を含んでいる可能性もあります(特に名称)。
対象
- D言語の基本構文を理解している方。
まあ、D言語を書いたことがない方でも、多分コードは読めると思います(笑)。
- D言語に興味があるかた
D言語だとこんなことが出来るよ!ってのを示すので参考になれば幸いです。
1. 型推論が強力だからこれを使わない手はない -> 型パラメータ使おう
関数について
T square(T)(T x) if (__traits(isArithmetic, T)) {
return x * x;
}
このように宣言することで、関数を呼び出す際にType square(Type)が生成され呼び出されます。
ここでポイントは、2つあります。
一つは、型の宣言について
関数名(型パラメータリスト)(引数リスト)と宣言します。
void func(T)(T x);
このような宣言の関数があるとすると、
func(1) => void func(int)
func("str") => void func(string)
このようになります。
また、型パラメータに自分で型を渡すことも可能です。
T func(T)(T x){
return x;
}
unittest{
double f = 1.042;
func(f); // type of return value is double
func!float(f); // type of return value is float
assert(func!float(f) != func(f));
}
もう一つは、引数リストの宣言と中括弧の間のifについてです。
ここで何をしているかというと、この場合squareは
数字リテラルのみを引数に取るべきです(つまり、stringやcharをとってはいけません)。
ですから、適用可能な型に制約を加えるべきです。
isArithmeticは対象が数字リテラルであるかどうかを評価します。
次に、構造体とクラスでの例です。
import std.algorithm.iteration,
std.array;
struct TypeContainer(T){
private T[] array;
T getValue(uint index){
if(array.length < index)
throw new Error("Error : array.length < index");
return array[index];
}
void insertValue(T newValue){
array ~= newValue;
}
void deleteValue(T targetValue){
array = array.filter!(element => element != targetValue).array;
}
void showAllElement(){
array.each!writeln;
}
}
こんなかんじの構造体があったとします。
これはまぁ、適当に配列をハンドリングする感じの構造体です。
それで、この構造体をまるごと型ごとに生成するのって最高にめんどくさいですよね?
例えば、配列に要素を追加する演算子は、array ~= value
で、すべての型で共通です。
それならば、共通部分はくくりだすべきです。つまりは、コードを再利用するべきです。
で、今回のプチポイントは次項で説明するstd.algorithm.iterationのテンプレートを使ったことです。
iterationを用いると、Rangeを上手いこと扱えて最高に気持がいいです。
詳しくは次項で!
2. std.algorithm.iteration
is 最高 -> 簡潔なコードを書こう!
ここではRangeの代表として、配列に関する操作で例を示します。
まず、配列の要素を1つずつ取り出して
関数f
にその要素を引数として渡して実行するケースを考えます。
前提として関数f
とint[]
の配列array
を次のように定義します。
int f(int x){
writeln(x * 2);
return x * 2;
}
int[] array = [1, 2, 3, 4, 5];
以下、この元で話を進めていきます。
ではまず、一般的(?)なforeach
を用いた例
foreach(element; array)
f(element);
まあ、こんなかんじになりますよね
では次に、std.algorithm.iteration.each
を用いた例を示します。
array.each!f;
これだけです!
すごく簡潔に記述できますね。
まぁ、こんなかんじの簡単な関数の場合、
わざわざ関数を定義するまでもないので無名関数でもいいかもしれないですね。
array.each!((x){
writeln(x * 2);
return x * 2;
});
他にも、map
, filter
, reduce
などなど強力なテンプレートが用意されています。
参考までに、拙作の暗号化プログラムの鍵ファイルを読み込むところの処理を例としてお見せします。
前提
鍵ファイルの仕様
鍵ファイルは以下の様なフォーマットとする。
100, 101, 102, 103, 104, 105,
200, 201, 202, 203, 204, 205,
300, 301, 302, 303, 304, 305
(数値がカンマ区切りで改行を含めて記述されている)
読み込む関数の仕様
引数はstring
型で鍵ファイルへのパスを受け取る、
戻り値はubyte[]
型で読みんだ鍵ファイルのデータを返す。
以下に、以上の仕様を満たす関数loadKeyFile
を宣言する。
ubyte[] loadKeyFile(string keyFilePath);
実装
ubyte[] loadKeyFile(string keyFilePath){
return File(keyFilePath, "r").byLine
.map!chomp
.join
.split(",")
.map!(e => e.removechars(" "))
.map!(e => cast(ubyte)e.to!ulong).array;
}
このように書くことが出来ます。
これもforeachを用いて記述することは可能ですが、気がすすみません....
なぜ気が進まないのか、ですけど。
一番大きいものにforeachは式ではありません、すなわち値を返しません。
繰り返しの制御構文です。
つまり、値を返さないということです。
ですから、一時的な変数を用いて値をやりくりする必要が出てきます。
一度しか使われないような変数は基本的に除去すべきです(大抵の場合そうしたほうが簡潔に分かりやすいコードになります)。
参考までに、foreachで書いた例を示します。
ubyte[] loadKeyFile(string keyFilePath){
string[] lines;
foreach(line; File(keyFilePath, "r").byLine)
lines ~= line.chomp;
string[] lineSplit = lines.join.split(",");
string[] lineRemovedSpace;
foreach(element; lineSplit)
lineRemovedSpace ~= element.removechars(" ");
ubyte[] returnArray;
foreach(element; lineRemovedSpace)
returnArray ~= cast(ubyte)element.to!ulong;
return returnArray;
}
どうですか?
可読性を意識して記述したため少し長くなりました(配列の値を直接書き換えれば幾つかの変数を減らすことができる)が、それでもコードが冗長になります。
余談ですが、今思えばつい数ヶ月前の僕だったらこのように書いてしまっていたと思います...
簡潔で無駄がなく分かりやすい、それが良いコードではないでしょうか。
3. 無名関数で変数を初期化する
Twitter4D(拙作のD言語用Twitter APIラッパー)での実際の例を示します。
string method = (){
if(type == "get" || type == "GET")
return "GET";
else if(type == "post" || type == "POST")
return "POST";
else
throw new Error("Method Name Error");
}();
これはなにをしているのか、ですけど
これはmethod
に"GET"
か"POST"
を代入するっていう処理なのですが、
どうしてこのように書くと嬉しいのかについて説明します。
じゃあこのように書かなかった場合を以下に示します。
string method;
if(type == "get" || type == "GET")
method = "GET";
else if(type == "post" || type == "POST")
metohd = "POST";
else
throw new Error("Method Name Error");
このようになります。
ここで注目すべきは、method = "value"
という処理が2回あることです。
個人的にはこのように書くと、変数が使いまわされているような感じがして好みではありません。
また、無名関数を用いることでその変数の値を決定する処理を変数と一塊にすることが出来ます。
わかりにくいかもしれないですけど、値の決定を行う処理は本質的な処理ではありません。
本質的な処理にはその値は必要ですが、あくまでもその値が重要なのです。
変数の値を決定する処理は重要ではないのです。
もっと意味分からないことを言ってしまったかもしれませんね...
少し別の見方をすると、名前空間を汚さないで済むというのがあります。
というのも、先ほど言ったように変数(定数)の初期化は処理の本質ではありません。
ですから、本質ではない処理を行うにあたって名前空間を汚すのはあまり好まれることではありません。
まぁ、この項で述べていることは完璧に僕の個人的な考えや感覚での話ですので人によっては無名関数を用いない方法を好まれるかもしれませんね。
つかれた
まだまだ、書きたいことはありますが
少し疲れたのでまたチマチマと時間を見つけて追記していきたいと思います。