ホーム‎ > ‎D言語入門‎ > ‎

D言語入門 - 入門編

はじめての言語としてのD言語

初めてプログラミングに触れる人のためにD言語を紹介します。受講生のニーズに合わせ、主に数学的な題材を用います。

インストール

必要に応じて su (を先に実行しておく) か sudo (をコマンドに前置する) しましょう。手順 1-2 で指定するファイル名は記事を書いたときのものなので、実習用マシンの Debian へインストールする場合は 'amd64.deb' で終わる名前の最新ファイルに置き換えてください (06/30追記)
  1. D言語の公式サイト から 'dmd_2.063-0_amd64.deb' をダウンロードする。
  2. 端末を開いてダウンロードしたファイルのあるフォルダへ移動し dpkg -i dmd2.063-0_amd64.deb を実行する。
  3. 依存関係でエラーが発生する (必要なものが揃っていない) ので、apt-get -f install を実行する。

これでインストールが完了するはずです。 端末で dmd とか rdmd と入力すると、コンパイラのヘルプが出てきます。

サンプル

最初のサンプルにしては長いですが、見てみましょう。

import std.stdio; int collatz(int n) { if (n == 1) { return 0; } if (n % 2 == 1) { return 1 + collatz(3 * n + 1); } else { return 1 + collatz(n / 2); } } void main() { "Hello D!".writeln(); sumSquare(10).writeln(); collatz(27).writeln(); } int sumSquare(int n) { int sum; foreach (k; 0..n) { sum = sum + k * k; } return sum; }

最初の行

最初の行からして意味わかりませんね。

import std.stdio;

これはおまじないです。 ということにしてもよいのですが、きちんと見てみましょう。 まず import は輸入ですね。 何かをもらってくるために使います。 std および stdio は何かの略です。 std は standard の略で、標準ライブラリの一部であることを示します。 stdio は standard input/output の略で、標準入出力についての機能たちを表します。

実行

やっぱりわかんないですね。 コードだけ眺めても仕方ないので、実行してみましょう。 上のコードを hello.d という名前で保存したとします。 端末で

dmd -run hello.d

とすると実行されます (06/30修正)。D言語は本来コンパイル言語なので、コンパイルしてから実行するには

dmd hello.d
hello

とします。こうするとコンパイルされたオブジェクトファイル (hello.o), バイナリ (hello) が作業中のディレクトリに残りますが、dmd -run はバイナリを直ちに実行してオブジェクトファイルとバイナリを削除するような動作をします (実際にはオブジェクトファイルとバイナリは一時フォルダに置かれ、実行が終わると削除されます)。

さて、

Hello D!
285
111
と表示されれば成功です。

標準出力

3行の出力がありました。 そういえば、先ほどのプログラムの一部を見ると

"Hello D!".writeln();
sumSquare(10).writeln();
collatz(27).writeln();
となっています。 writeln はどうやら「1行の出力」に対応していそうです。 write は「書く」で、ln は line = 行の略です (直線じゃないよ)。

この出力が標準出力というやつで、ほとんどの対象に .writeln(); を後置すると画面に出力してくれます。

main

少し引いて見ると

void main()
{
    "Hello D!".writeln();
    sumSquare(10).writeln();
    collatz(27).writeln();
}
となっていました。 このように、プログラムは {} によって区切られます。 対応する {} に内側を含めたものをブロックといいます。 その前の void main() というのが重要で、これは「この後に続くブロックを実行してね」という指令になります。
文字列

"Hello D!" というのは、そのまま画面に出力されていることからして、基本的な対象ということになります。 これは文字列リテラルです。 基本的には、ダブルクオート "" の内側自体を指します。 ただし一部の特殊な文字を表示するにはエスケープという操作が必要です。 詳しくは TA へ尋ねるか「文字列 エスケープ」あたりでググりましょう。

その他の部分

まだ collatz とか sumSquare は意味がわかりませんね。 いや、実は意味がわかってると嬉しいです。 いい名前をつけましたので。 sum は和だし、Square は平方です。 そういえば出力の285は連続した平方数の和で表せます。 Collatz 予想は知ってますよね。 27 って Collatz 予想を知った時に試しませんでした?

collatz 関数

先に collatz という名前のついた行と、直後のブロックを見てみます。

int collatz(int n)
{
    if (n == 1)
    {
        return 0;
    }
    if (n % 2 == 1)
    {
        return 1 + collatz(3 * n + 1);
    }
    else
    {
        return 1 + collatz(n / 2);
    }
}
これは関数の定義です。 実は先ほどの void main() で始まるのも関数だったのですがね。 最初の int は、関数の終域 (codomain) が int という集合 {-2^31, ..., -1, 0, 1, ..., 2^31-1} であることを表しています。 絶対値が40億くらいまでの整数を扱うには十分です。 これでは足りない方は long を試してください。 こちらは {-2^63, ..., 2^63-1} です。

後ろの (int n) は、関数の始域 (domain) が int であることを表しています。 数式っぽく書くと、カッコ内は 'int ∋ n' で、外側は 'int ∋ collatz(n)' を意味します。

ちなみに、実数なんてものは存在しません。 浮動小数点数を扱うには floatdouble を使います。 表される値の範囲が異なるのですが、広い側の double だけ使うことをおすすめします。

少し脇道にそれましたが、関数の宣言部分 int collatz(int n) を説明しました。 それでは中身を見ていきます。

if (n == 1)
{
    return 0;
}
if (n % 2 == 1)
{
    return 1 + collatz(3 * n + 1);
}
else
{
    return 1 + collatz(n / 2);
}
if 文

最初の if (n == 1) と続くブロックは if 文 です。

if (IfCondition)
{
    ThenStatement
}
は、IfCondition の成り立つならば必ず、またその場合に限り ThenStatement を実行せよという意味です。 IfCondition には n == 1 が、ThenStatement には return 0; が代入されています。

n == 1 は等式です。 等式は等号を2つ重ねて表すので注意してください。 等号1つには別の意味があります。

return 0; は「0 を返せ」という命令になります。 返すというのは「これを関数値として確定する」ということだと思ってください。

次に if (n % 2 == 1) で始まる if 文があり、さらに else とブロックが続いています。

if (IfCondition)
{
    ThenStatement
}
else
{
    ElseStatement
}
これは上の if 文を拡張したもので、IfCondition が成り立たないならば (何もしないかわりに) ElseStatement が実行されるという点のみが異なります。
ThenStatementElseStatement が単独の文からなるときは、中括弧を省略することができます。これは後で出てくる foreach range 文でも成り立ちます。したがって上のコードは
if (n == 1)
    return 0;
if (n % 2 == 1)
    return 1 + collatz(3 * n + 1);
else
    return 1 + collatz(n / 2);
と等価です。しかし、複数の制御文を重ねる時などは ('else 節は最も近い if 文に対するものと解釈される' という文法を知らないと) 意味が曖昧になることがあります。中括弧の省略は控えめにすることをおすすめします。
基本的な演算子
IfConditionn % 2 == 1 となっています。 (2つ重なった) 等号の左辺は n2 で割った余りです。 プログラミング言語の多くは割り算の余りを、0でなければ被除数 (ここでは n) と同符号にとることにしています。 余談ですが Python では除数 (ここでは 2) と同符号ですが、他には知りません。

n2 で割って 1 余る数、つまり正の奇数 (ただし上で既に終わっている1以外) のときは返すものが変わります:

return 1 + collatz(3 * n + 1);

これは n の3倍に1を加えて collatz 関数に渡し、その値に1を加えた値を返すということになります。

基本的な演算子を紹介しておくと、+, -, *, /, は加減乗除、% は剰余、^^ は冪乗です。 詳しく知りたい方は Expressions - D Programming Language (English)式 - プログラミング言語D (日本語) を参照してください。

さて、n が他の値のときにはどうするのでしょうか。

return 1 + collatz(n / 2);

n を2で割って collatz へ渡し、その値に1を加えて返します。

問題
  • collatz(1), collatz(2), ..., collatz(10) の値を手計算で求めてください。
  • collatz(0), collatz(-100) を実行するとどうなるか考えてみましょう。

sumSquare 関数

それでは、関数 sumSquare の定義も見てみましょう。 これは最後に書いてありますが、どこに書いてもいいのです。 本当のスクリプト言語 (ほとんどはインタプリタ型) と違って良いところの1つは、関数を書く順番を気にしなくてよいということです。 これを強調するために collatz, main, sumSquare の順に記述してみましたが、行儀のよい書き方としては定義してから使う (ボトムアップ) か、主要な main から順に並べる (トップダウン) がよいでしょう。

int sumSquare(int n) { int sum; foreach (k; 0..n) { sum = sum + k * k; } return sum; }

これも関数の定義で、collatz と同様に int から int への関数 sumSquare を定義しています。 それでは、本体を見てみましょう。 これは3つの部分からなります。

変数定義
int sum;

これは int の元 sum をとるという感じです。 といっても任意にとるわけではありません。 整数の初期値は0です。

foreach range 文
foreach (k; 0..n) { sum = sum + k * k; }

この foreach (k; 0..n) で始まるブロックは厄介です。 しかし簡単な英語の論文を読んでいる気分になれば大丈夫。 これは 'for each k in [0..n)' の意味です。 0 以上 n 未満k について、何かを実行します。 さて、何と書かれているでしょうか。

代入文

簡単な方程式に見えます。 sum = sum + k * k; ということはもちろん k = 0 かと思いますが、これは方程式どころか等式でもありません。 = は代入を表す演算子です。 右側に sum が現れていなければ、これは定義と思うこともできます。 'let S = s + k * k' のように。

これはなかなか難しいのですが、代入演算子はまず右辺を評価します。 最初 sum, k の値はともに 0 です。 これらの値を使って右辺を計算するのです。 その結果を、左辺に書いてある変数に代入します。 sum には 0 が代入されますが、もともと 0 ですから値は変わりません。

次に k に1が入った状態でまわってきます。 右辺を計算すると 1 になりますね。 したがって sum の値は 1 に書き換えられます。

次に k に2が入った状態でまわってきます。 右辺を計算すると 1+2*2 = 5 になり、sum は 5 に書き換えられます。

次は k に3が入り、右辺を計算すると 5+3*3 = 14 になり、sum が 14 に変わります。

同様にくり返して k が 9 のところでは sum は 285 になります。 n が 10 になると最初に指定した範囲から外れるので、sum の更新に到達する前に foreach (k; 0..n) の繰り返しを終わります。

問題
  • sumSquare(-10) を表示してみましょう。その値になるのは、なぜでしょうか。
  • sumSquare の内部を変更して、foreach を使わずに和を (公式によって) すぐに返すようにしてください。
  • if (a == b) if (b == c) "All are equal".writeln(); else "a is different from b"; というコードを main の中に配置し、その前で整数 a, b, c を定義・値を代入して実行できるようにしてください。a, b, c の値を変えていくつか試し、このコードのおかしな挙動を修正してください。
Comments