こんにちは。開発担当の渡部です。
普段の業務では Web のフロントエンド・サーバーサイドをやりつつ、オフの時は Oculus Rift を触っており Developers Summit 2014 などのイベント等にも多数参加していますが、
本日はジワジワと伸びているプログラミング言語についてのお話です。
つい先月、このブログでも取り上げた Objective-C 後継として開発中の Apple Swift や、 JavaScript を置き換えるものとして作られている Google の Dart や Microsoft の TypeScript などのように、既存の言語に限界を感じて新しい言語を作るというケースは非常に多いです。
今回はその中から、ハードウェアレベルからアプリケーションまで扱えるシステムプログラミング言語として不動の地位を築いているC言語の後継という大きな目標を掲げて開発されているD言語について紹介させて頂きます。
D言語とは
D言語はアメリカのウォルター・ブライト (Walter Bright) 氏によって設計されたネイティブコードを出力するプログラミング言語です。ウォルター氏は古くからコンパイラ開発者として有名で、D言語を創る前にもC言語コンパイラや世界で最初の C++ コンパイラの開発などで知られています。
彼はその経験からC言語(1972〜)や C++ (1983〜)の長所を残しつつ、実用上避けられないバッドノウハウを解消した新しいプログラミング言語として、1999 年にD言語の開発を開始しました。
C++ はC言語とソースコード互換性を持っているためC言語ユーザーがそのまま移行でき、多くのソフトウェア資産を活用できるというメリットがありますが、そのためC言語が持つデメリットも多く引き継いでしまっています。そこでD言語はC言語とのソースコード互換性を捨てた代わりに、 C/C++ では言語機能に含まれていない数多くの機能を含んでいます。
D言語はそういった理想主義的なコンセプトで開発されていたため、非常に難産だった言語でもあります。 2001 年に最初のバージョンである 0.001 がリリースされ、バージョン 1.0 のコンパイラが出たのは6年後の 2007 年です(現在のバージョンは 2.065 )。そのためD言語は開発から 15 年経った現在も未だマイナーな言語でしたが、2013 年に Facebook の社内ツール開発に採用されたことが発表されたり、次世代ゲーム機用ソフトでの開発で採用されるなど、徐々にメジャーな採用例が増えてきています。
D言語の機能
D言語はC言語の後継を目指して作られていますが、上記の通り C++ や Swift、TypeScript などと違って元になったC言語とのソースコード互換性は持っておらず、そのため開発から15年経過した現在では「C言語とは全く違う文法」も豊富に存在しています。そのため、ここではD言語の機能についてごく一部の主なものを紹介します。
プリプロセッサを廃止して言語機能に
C/C++ ではコンパイラとは別にプリプロセッサが存在しており、プリプロセスによるソースコードの書き換え→コンパイルという手順を踏みますが、D言語ではプリプロセッサ相当の機能が言語機能として含まれており、柔軟な構文かつ短いコンパイル時間に繋がっています。
//C/C++ // 標準入出力ヘッダの読み込み #include <stdio.h> #include <iostream> // MY_TYPEをプリプロセスでintに置換する #define MY_TYPE int // WINDOWSが定義されていた場合のみヘッダを読み込む #ifdef WINDOWS #include <windows.h> #endif // プリプロセス時の分岐処理 #ifdef SET_MY_TYPE #define MY_TYPE int #endif
//D // 標準入出力モジュールの読み込み import std.stdio; / // int型にmy_typeという別名を定義 alias int my_type; // Windows向けコンパイルでのみWindows用モジュールを読み込む // Windowsはコンパイラ定数 version(Windows) { import std.c.windows.windows; } // 定数を用いたコンパイル時に分岐される処理 const bool set_my_type = true; static if(set_my_type) { alias int MY_type; }
言語組み込み型の強化
C言語や C++ では型の最大値や配列の長さを調べるために標準ライブラリで #define されたキーワードや sizeof() 関数、もしくはテンプレート関数 (std::numeric_limits) などを使って確かめる必要があります。
D言語の場合は全ての型が組み込みプロパティを持っており、int.max、float.epsilon、array.length などが言語仕様で定義されているので、これらを参照するコードが非常にスッキリとします。もちろんユーザー定義のプロパティを作成することも可能です。
//D struct MyStruct { // read @property int Number() { return m_num; } // write @property int Number(int value) { return m_num = value; } private: int m_num; } // [変数名.プロパティ名]で取得も代入も可能 MyStruct my_struct; my_struct.Number = 123; int x = my_struct.Number;
洗練されたクラス構文
C++ は80年代に設計された言語のため、Java や Ruby、C# などの最初からオブジェクト指向で設計された言語と比べ冗長な宣言が目立ちます。それに対してD言語は Java と Ruby よりも後のオブジェクト指向開発が普及した時期に設計されたため、this メソッドや super メソッドなど現代的なキーワードを用いて効率的にクラスを定義できます。
//C++ class ParentClass { // コンストラクタはクラス名と同じ ParentClass() { //... } } class ChildClass : ParentClass { // ベースクラスのコンストラクタ呼び出し ChildClass() : ParentClass() { //... } }
//D class ParentClass { // コンストラクタはthisで定義 this() { //... } } class ChildClass : ParentClass { this() { //... // 任意のタイミングでベースクラスのコンストラクタを呼び出せる super(); //... } }
//C++ namespace mynamespace { int num; } using mynamespace::num; num;
//D // ファイル単位でモジュールが定義されるため{}不要 module mymodule.modulechild; int num; import mymodule.modulechild; alias mymodule.modulechild.num num; num; // 暗黙的に呼び出し側スコープの同名メンバを上書きしない
ガーベージコレクション
D言語はネイティブコードを出力する言語としては珍しく、ガーベージコレクション(以下GC)を搭載しています。
最近では Google の Go や Mozilla の Rust など、GC を採用したネイティブコード出力のプログラミング言語が増えていますが、D言語はその先駆けと言えるでしょう。GC の搭載によって、連想配列のようにスクリプト言語やVM環境の言語でお馴染みの便利な機能をメモリ操作を気にせず安全に扱えます。
逆に言うと、GCはメモリを自分で管理できないのでパフォーマンス上の問題で使いたくない方も居るかと思います。しかし、D言語の場合は new を使ったインスタンスの作成、動的配列の操作、連想配列、 delegate などをコードから取り除けば GC はほぼ実行されませんし、 core.memory.GC.disable() でGCの自動実行を無効化すれば任意のタイミングでGCを実行できます。
int[2] などのように長さを指定して配列を作成すれば軽量な静的配列を定義できますし、静的配列に対して範囲外アクセスしようとするとコンパイル時にエラーとなるので実行時の安全も保たれます。D言語では構造体も Plain Old Data 型で参照型のクラスとは区別されています。
ユニットテスト
ここ最近は Web であろうとアプリケーションであろうと、ソースコードのテストをやらないということは考えにくいと思います。ですが、ユニットテストを実行するためにはテストフレームワークを用意してテストをセットアップするという作業が必要なため、そんな事をしている暇が無いとテストをやっていない方も未だ多いのではないでしょうか。
D言語の場合はフレームワークや標準ライブラリではなく、言語機能自体にユニットテストが組み込まれており、ソースコードの任意の位置にユニットテストを埋め込むことが可能です。
//D int mul (int x, int y) { // 掛け算をしたいのに足し算。。。 return x + y; } unittest { // 0+2=2となってしまうので間違い assert(mul(0,2) == 0); }
上記のコードを
dmd -unittest -run unittest.d
として実行すると、コンパイラは実行ファイルに unittest 内のコードを生成し、実行時に assert のエラーを解釈して例外を発生させます。さらに、assert の前に static キーワードを付けるとコンパイル時に assert が mul を実行し、例外の発生によってコンパイル自体が中止されます。
D言語ではこのようにシンプルなユニットテストをコンパイラだけで実行することができ、よりリッチな機能を持つテストフレームワークの実装も勿論可能です。
まとめ
D言語にはこの他にも数多くの言語機能があり、また今回は紹介できませんでしたが豊富な機能を持った標準ライブラリ群があります。C言語とのソースコード互換性はありませんが、基本型ではポインタを含めてバイナリレベルの互換性を持つため、枯れたC言語製のライブラリをリンクして実用的なアプリケーションを作成することも可能です。 C++ 製ライブラリも同じく条件を満たせばD言語から利用することが可能です。
トレンド的な話ではウォルター氏を含むD言語プログラマーが Facebook に雇用されてD言語の開発とD言語製アプリケーションに携わっている状態で、D言語公式カンファレンスの DConf も Facebook 本社を会場として去年から開催されています。
「C言語(C++) を使っているけどネイティブじゃない他の言語はどうにも…」という方や「スクリプト言語を使っているけどC言語や C++ を今から覚えるのは…」という方は試しにD言語を触ってみてください。
おすすめリンク
日本語訳の方には内容が古いものや未翻訳のページもありますので、基本的に英語公式サイトを参照することをお勧めします。
TIPS・実装例が豊富に掲載されています。
D言語で開発された非同期 I/O ベースの Web フレームワークです。
D言語のパッケージマネージャ DUB のリポジトリに登録されているパッケージの一覧です。
現在日本で唯一販売されているD言語書籍です。
フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!