D 1.0   D 2.0
About Japanese Translation

Last update Mon Feb 9 01:08:28 2009

概要

D って何?

Dは、汎用のシステム/アプリケーション プログラミング言語です。 C++以上の高級言語ですが、高速なコードを書く能力や、 OS の API やハードウェアへの直接アクセスの機能も維持しています。Dは、 中規模から100万行単位の大規模ソフトウェアをチームで開発するのに適しています。 Dは学習が簡単で、 プログラマを補助する沢山の機能を提供し、 コンパイラの強力な最適化技術と相性の良い言語です。 D Man

Dはスクリプト言語でも、 インタプリタ言語でもありません。 VM を持たず、 宗教やおおげさな哲学とも縁がありません。 信頼性・保守性の高い・読みやすいコードを書いて仕事をサクサク進める必要のある 現実的なプログラマのための、 現実的な言語です。

D は、様々な言語のコンパイラを実装し、 その言語で大きなプロジェクトを構築してきた数十年の経験の集大成です。 D はそれらの言語(特に、C++)からインスピレーションを受け、 経験によって、 現実の実用性の面から調整して取り込んでいます。

なぜ、 D ?

なぜ、まったく、誰がまた新しいプログラミング言語を必要とすると言うのでしょうか?

ソフトウェア産業は、C言語が開発されて以来、 長い道を歩んできました。 沢山の新しい概念が C++言語 に盛り込まれましたが、C との互換性、 特にCの設計上の欠点もほとんど全て、そのまま保たれています。 それらの欠点を修正しようという試みは多くありましたが、 しかし、互換性の問題が足かせになっていました。 一方で、CもC++も、新機能の増大を経験しています。 これら新機能は、 古いコードを書き直す必要がないように、 既存の機構に注意深くはめ込まれなければなりませんでした。 その結果は、物凄い複雑さ - C言語の標準規格は約500ページ、 C++ に至っては 750ページ! - です。 C++ は実装するのにコストがかかり難しい言語で、 その結果、異なる実装の間で完全に移植性のある C++ コードを書くのはかなりイライラする作業となってしまいました。

C++プログラマは、言語の一角に住み着いてプログラムする傾向があります。 つまり、ある特定の機能には熟練しても他の機能を避けて通っていたり。 コンパイラからコンパイラへとコードを移植することはできても、 プログラマからプログラマへコードを渡すのが難しくなっています。 C++の強力さは、 種々の根本的に異なったプログラミングスタイルをサポートすることです …が、長い目で見ると、スタイルの間の重複や矛盾が障害となってきます。

C++では、可変長配列や文字列の結合といった機能は言語の一部ではなく、 標準ライブラリとして実装されています。 言語のコアの一部でないということは、 幾つか 最適でない結果 をもたらします。

C++のパワーと可能性を取り出して再設計し、 単純で、 直交性のある、 実用的な言語へと変換することは可能でしょうか? その言語を、 コンパイラ作成者が正しい実装を簡単に書けるように、 そして積極的に最適化を進めたコードを生成できるように、 まとめあげることは果たしてできるのでしょうか?

現在のコンパイラ技術は進歩の結果、 かつて未熟だったコンパイラ技術を補うために存在した 幾つかの言語仕様を省くことができるようになりました。 (例としては、 C言語のキーワード'register'であったり、 もっと微妙な例ではCのマクロプリプロセッサがあります。) 私たちは現在のコンパイラ最適化技術に頼って、 原始的なコンパイラからまともな品質のコードを得るために必要だった言語仕様から 抜け出すことができます。

D言語の主な目的

C/C++ から引き継いだ特徴

D の見かけは、全体的に C や C++ に似ています。これによって、 Dを学習したりコードを移植したりするのが容易になります。 C/C++ から D への以降は自然で、 新しいことを丸々学んだりする必要がありません。

Dを使うと言うことは、 プログラマが特別な実行時VM (仮想マシン) に縛られる Java VM や Smalltalk VM のような機構とは無関係です。 D VM なんてものは存在せず、 D は普通にリンクできるオブジェクトファイルを生成する、 直接的なコンパイラです。D は C と同様にOSに直結しています。 make のような一般的なツールも、 Dの開発にピッタリ使うことができます。

取り除かれた特徴

D を必要とする人は?

D を必要としない人は?

D の主要な特徴

この節では、様々なカテゴリにわたる、 D の興味深い特徴をリストアップします。

オブジェクト指向プログラミング

クラス

D のオブジェクト指向的特徴は、 クラスによるものです。 継承のモデルは、 単一継承 + インターフェイス となっています。 Objectクラスが継承階層のルートに存在し、 全てのクラスがその機能を実装します。 クラスは参照としてインスタンス化されるため、 例外発生時の複雑なクリーンアップコードが必要でなくなります。

演算子オーバーロード

クラスは、新しい型をサポートするよう型システムを拡張することで、 既存の演算子と共にはたらくように作ることができます。 例としては、bignumber クラスを作って、通常の代数的な構文を使えるよう、 +, -, *, / 演算子をオーバーロードする、などがあります。

生産性

モジュール

ソースファイルは、モジュールと1対1に対応しています。 ファイルから宣言をテキストとして #include する代わりに、 単にモジュール内のシンボルを import します。 複数回同じモジュールをimportする心配は不要で、 ヘッダを #ifndef/#endif で囲うラッパや #pragma once などの必要がなくなりました。

宣言 vs 定義

C++という言語は通常、関数やクラスを2回宣言することを要求します - .h ヘッダファイルに書く宣言と、.c ソースファイルに書く定義と。 これはエラーを生みやすく、退屈な作業です。明らかに、 プログラマが記述する必要があるのは1回だけで、 あとはコンパイラがシンボルのimportに必要な宣言情報を取り出せばよいのです。 そしてそれが、まさしくDのする動作です。

例:

class ABC
{
    int func() { return 7; }
    static int z = 7;
}
int q;

もはや、メンバ関数やstaticメンバ、externなどの定義を分けて書く必要はありません。 次のようなわずらわしい構文も不要です:

int ABC::func() { return 7; }
int ABC::z = 7;
extern int q;

注:もちろん、C++ でも { return 7; } のような単純な関数は インライン で書けます。が、複雑なものはそうではありません。それに加え、 前方参照がある場合は関数のプロトタイプ宣言が必要になります。 次のコードはC++では動作しません:

class Foo
{
    int foo(Bar *c) { return c->bar(); }
};

class Bar
{
  public:
    int bar() { return 3; }
};

しかし、これと同等のDのコードは動作します:

class Foo
{
    int foo(Bar c) { return c.bar; }
}

class Bar
{
    int bar() { return 3; }
}

Dの関数がインライン化されるかどうかは、 最適化の設定によって決定されます。

テンプレート

D のテンプレートは、部分特殊化能力の提供によって、 ジェネリックプログラミングを綺麗にサポートします。 テンプレートクラスもテンプレート関数も利用可能で、 可変個引数テンプレートやタプルといった強力な機能を備えています。

連想配列

連想配列は、自然数のindexに制限されず、 任意の型の値を配列の添え字として使えるようにした配列です。 連想配列の本質は、ハッシュ表です。連想配列によって、 高速で効率的な、 バグのないシンボルテーブルが簡単に実装できます。

Real Typedefs

C と C++ の typedef は、本当は型の 別名 に過ぎず、 新しい型は実際には導入されていませんでした。D は、本物のtypedefを実装しています:

typedef int handle;

このコードは、実際に新しい型 handle を作成します。 型チェックが働き、typedefされた型で関数をオーバーロードできます。 例えば:

int foo(int i);
int foo(handle h);

ドキュメント化

ドキュメント化は、従来、2つのステップに分かれて行われてきました - まずコメントとして機能の説明が書かれ、 それとは別のhtmlやmanページとして改めて書き直されるという形で。 当然の帰結として、時間がたつにつれ、コードは更新されても分離されたドキュメントは 更新されず、次第に説明と実態が乖離していくようになります。 ソースに書き込まれたコメントから 必要なドキュメントを直接生成できるようにすることで、 ドキュメント書きの時間を半分に減らせるだけでなく、 コードとドキュメントの一貫性を保つのもずっと簡単になります。 Ddoc が、Dのドキュメント生成の仕様となっています。 このページもDdocによって生成されています。(※訳注:原文はDdocで生成されていますが日本語訳は実は手打ち(^^;)

C++にもサードパーティ製のツールはいくつか作られましたが、 それらには重大な欠点がありました:

関数

D には、グローバル関数、関数のオーバーロード、インライン化、 メンバ関数、仮想関数、関数ポインタ…など、 従来通りの関数は期待されるとおりにサポートしています。 それに加えて:

ネストした関数

関数は他の関数の中にネストすることができます。 これは、コードの局所性や、 関数クロージャを使うテクニックで有用です。

関数リテラル

無名関数を式中に直接埋め込めます。

動的クロージャ

ネストした関数やクラスのメンバ関数は、 クロージャ(デリゲートとも呼ばれます) として参照でき、ジェネリックプログラミングをより簡単で型安全にします。

in, out, inout 引数

これらの引数を指定するのは、 関数の可読性を上げるだけでなく、 何も失うことなくポインタの必要な場面を減らし、また、 コンパイラがコードの問題を発見しやすくする可能性を残します。

これによって、D を様々な外部のAPIと直接繋ぐことが可能になります。 "インターフェイス定義言語 (IDL)" のような対策は不要になるでしょう。

配列

Cの配列には、いくつか直すべき欠点があります:

Dの配列には、 ポインタ、静的配列、動的配列、連想配列といった種類があります。

参照: 配列

文字列

文字列の処理は非常にありふれた作業ですが、C/C++では扱いにくいものになっています。 この機能は、言語の直接サポートが必要です。 モダンな言語は文字列の連結やコピーなどをサポートしますが、D も同様です。 文字列周りの改善は、配列の扱いを改善したことの直接の帰結です。

リソース管理

自動メモリ管理

Dのメモリ割り当ては完全にガベージコレクタの下で行われます。 C++では経験的に、メモリ解放を管理するためには複雑な機能が 沢山必要であることがわかっています。ガベージコレクタによって、 言語は非常に簡単になります。

今は、ガベージコレクションは怠け者や初心者プログラマのためのものだ、 という認識があります。これと同じことが C++ に対しても言われていたのを思い出します。C++にできてCでできないこと、 もっと言えばアセンブラでできないこと、は結局の所存在しないのだそうな。

ガベージコレクションは、 CやC++では必要だった、 面倒な、 エラーの元になるメモリ割り当て管理のコードを不要にします。 これは開発時間を早め保守のコストを下げるだけでなく、 できたプログラムもしばしばより高速に動作するようになるのです!

もちろん、C++で使えるガベージコレクタはありますし、 実際私も自分の C++のプロジェクトでは使っています。しかしながら、 この言語はGCとの親和性が悪く、その効果性は十分発揮されません。 ランタイムライブラリの大くもGCと同時に使うことができないのです。

より完全な議論については、ガベージコレクション の項をご覧下さい。

明示的なメモリ管理

DはGCを採用した言語ではありますが、 特定のクラスでは new/delete 演算をオーバーライドして、独自のメモリ割り当てを行うことができます。

RAII

RAII は、リソースの管理と解放を管理するための、 モダンなソフトウェア開発技術です。 Dは、RAIIをガベージコレクションのサイクルとは独立の、 制御・予測可能な方法でサポートします。

パフォーマンス

Lightweightな構造体

D は、C形式の単純な構造体をサポートしています。これには、 Cのデータ構造との互換性と、クラスの能力が過剰であるときに使えるように、 との二つの理由があります。

インラインアセンブラ

デバイスドライバやハイパフォーマンスが必要なシステムアプリケーション、 組み込みシステムなど、特化されたコードはしばしば、 アセンブリ言語に浸って仕事をなしとげる必要があります。 Dの実装は必ずしもインラインアセンブラを実装する必要はありませんが、 インラインアセンブラは、言語の一部として定義されています。 ほとんど場合必要なアセンブラコードは扱えるので、別のアセンブラやDLLは不必要になります。

多くのDの実装は、Cでのそれに似た、 組み込み関数をサポートしています。 I/Oポートの制御や特別な浮動小数点数演算への直接アクセスなどができます。

信頼性

モダンな言語は、できる限り、プログラマがバグを発見する手助けとなるべきです。 様々な形の"手助け"があります。 よりロバストな技術を簡単に使えるようにしたり、 明らかに間違ったコードを実行時に検出させるフラグをコンパイラにつけるなどなど。

契約

契約プログラミング (Contract Programming。B. Meyer によって発明されました)は、 プログラムの正しさを保証する助けとなる革命的な技術です。 DでのDBCは、関数の事前条件、事後条件、クラス不変条件、 assert契約を含んでいます。 Dの実装については、契約 の項をご覧下さい。

単体テスト

単体テストをクラスへ追加して、プログラムの起動時に自動実行することができます。 この機能は、気付かぬ内にクラスの実装にバグが入っていないかを、 ビルドのたびに確かめる助けになります。 単体テストは、クラスのソースコードの一部です。 書き上げたコードをテスターに放り投げる従来のテスト方法とは対照的に、 テストを作ることが、自然にクラスの開発の過程に含まれるようになります。

単体テストは他の言語でも可能ですが、 言語自体がその概念にぴったり適合していないため、 しっくりこない結果になります。単体テストは D の最大の特徴です。 Dのライブラリ関数は、単体テストによって、実際に関数が動くことの保証と、 使い方の説明の両方がうまくなされています。

WebでダウンロードできるC++のライブラリや、 アプリケーションのコードを考えて下さい。 そのうちのどれだけが、単体テストはもちろん、*多少なりとも*検証を行っているでしょう。 1% 未満? 普通我々は、コンパイルが通ったなら、動作すると仮定しています。 そして、コンパイラが警告を吐くと、実際にバグなのかそれとも余計なお世話なのか、 疑問に思って悩む程度です。

単体テストを契約プログラミングとともに用いることで、D は、 信頼性のあるロバストなシステムを構築するための最高の言語となります。 単体テストは、手に入れたDのコード片の質の簡単な評価手段にもなります - つまり、もしそのコードに単体テストや契約が書かれていなければ、 却下。

debug属性・debug文

デバッグは、言語の構文の一部になりました。 デバッグコードは、マクロやプリプロセッサのコマンドを使わずとも、 コンパイル時に有効にしたり無効にしたりできます。 debug構文は、 リリース用とデバッグ用の両方のコードを生成しなければならない現実のソースコードを、 理解しやすく、一貫性と移植性のあるものにします。

例外処理

単なる try-catch ではなく、より優れた try-catch-finally モデルが使われています。finally を実現するためのダミーオブジェクトとデストラクタを作る必要はありません。

同期

マルチスレッドのプログラミングが、どんどん主流になってきています。 そこで、D はマルチスレッドのプログラムを作るための要素を提供します。 同期処理は、メソッドまたはオブジェクト単位でなされます。

synchronized int func() { ... }

同期された関数は、 一度に一つのスレッドだけが実行することができます。

同期された文は、周りにmutexを置くことで、 オブジェクト単位、またはグローバルにアクセスを制御します。

ロバストな技術のサポート

コンパイル時チェック

実行時チェック

互換性

演算子の優先順位、評価規則

D は、Cの演算子とその優先順位、 式の評価順序規則、 型の昇格規則を全て保っています。これにより、 C風に書いたのに実際の動作は違う、 という見つけにくいバグを避けることができます。

C API への直接アクセス

Dは、C の型に対応するデータ型があるだけでなく、 C の関数への直接アクセスを提供しています。 ラッパ関数や、パラメタの調整、集成体のメンバを一つ一つ設定するコード、 などは一切必要ありません。

C の全ての型のサポート

これによって、既存のCで書かれたライブラリやC形式のAPIとの インターフェイシングが可能になります。 Dは、構造体、共用体、 列挙型、ポインタ、C99の全ての基本データ型のサポートを含みます。 外部で規定されたデータ形式との互換性を保つために、 構造体メンバのアラインメントを設定する機能も備えています。

OS例外の処理

Dの例外処理メカニズムは、 動作するOSがアプリケーション の例外を扱う方法と結合されています。

既存のツールの利用

D は標準的なオブジェクトファイル形式のコードを生成します。これにより、 標準的なアセンブラ、リンカ、デバッガ、プロファイラ、exe圧縮ソフト、 その他の解析ソフトが使用でき、 他の言語で書かれたコードとのリンクも可能になります。

プロジェクト管理

バージョン付け

D には、 同じソースから複数のバージョンを生成する機能のサポートが組み込まれています。 これは C プリプロセッサでの #if/#endif テクニックに置き換わるものです。

非推奨属性

コードが発展して行くにつれ、古いライブラリのコードは、新しい、 より良いものに置き換わっていきます。 レガシーなコードに対応するためには古い版も残しておく必要がありますが、これらには deprecated という印をつけられます。deprecated な機能を使おうとするコードは 通常不正となりますが、コンパイラスイッチの設定によって許可もできます。 また、 メンテナンスプログラマが非推奨の機能への依存箇所を認識するのが簡単になります。

D プログラムのサンプル (sieve.d)

/* エラトステネスの素数ふるい */

import std.stdio;

bool[8191] flags;
 
int main()
{   int i, count, prime, k, iter;

    writefln("10 iterations");
    for (iter = 1; iter <= 10; iter++)
    {	count = 0;
	flags[] = 1;
	for (i = 0; i < flags.length; i++)
	{   if (flags[i])
	    {	prime = i + i + 3;
		k = i + prime;
		while (k < flags.length)
		{
		    flags[k] = 0;
		    k += prime;
		}
		count += 1;
	    }
	}
    }
    writefln("%d primes", count);
    return 0;
}