気持ちよさ
おひさしぶりです、ちかです
いつもながら記事全体が個人の感想です。
気持ちよくコーディングするためには
不安や恐怖をなくす必要があります。
- 呼んでる関数に余計な副作用があるんじゃなかろうか。
- 例外が飛んでくるんじゃなかろうか。
- 境界値で落ちるんじゃなかろうか。
- リソースの解放がダダモレなんじゃなかろうか。
- マルチスレッドはスリル、ショック、サスペンスなんじゃなかろうか。
- このソフトウェアは世界中の誰の役にも立たないんじゃなかろうか。
不安や恐怖がなくて、問題の解法に集中できて、秒単位で編集 - 実行ループできるなら、
コーディングは気持ちいいものになるのではないかと思います。
D 言語の場合
ということを踏まえて
D 言語で 2 つの非負整数の最大公約数を求めてみます。
# コンパイラーは Windows 版 DMD 2.0.65 を使っています。
- ユークリッドの互除法 - Wikipedia
- 入力を m, n (m ≧ n) とする。
- n = 0 なら、 m を出力してアルゴリズムを終了する。
- m を n で割った余りを新たに n とし、更に 元の n を新たに m とし 2. に戻る。
gcd.d
import std.typecons; unittest { assert(gcd(0, 0) == 0); assert(gcd(0, 1) == 1); // すべての整数は 0 の約数 assert(gcd(uint.max, 0) == uint.max); assert(gcd(1, 1) == 1); assert(gcd(21, 10) == 1); assert(gcd(18, 24) == 6); assert(gcd(2 * 2 * 3 * 5 * 7, 2 * 2 * 7 * 11) == 2 * 2 * 7); assert(30.gcd(75) == 15); } /// 2 つの非負整数の最大公約数を算出します。 pure nothrow auto gcd(uint x, uint y) { auto pair = x < y ? tuple(x, y) : tuple(y, x); while (pair[0] != 0) { pair = tuple(pair[1] % pair[0], pair[0]); } return pair[1]; }
アルゴリズムをそのまま書き下したかのようにシンプルですが
ここには安心と気持ちよさが詰まっています。
世界中の誰の役にも立たないでしょうけどね…(苦笑)
D 言語の機能紹介
- ■ unittest ブロック
-
動作に不安があるなら試しましょう!
unittest で始まるコードブロックは、
コンパイルオプション -unittest をつけたときだけコンパイルされ、
実行時にエントリポイント (main 関数) の前に実行されます。ファイルごとに単体でテストを走らせることもできますし、
「開発版では起動するたびに単体テストが走る」ようにもできます。関数やクラス定義の近くに unittest ブロックを置くことで、使用法の例示にもなります。
テストをパスすると気持ちいい!
- ■ pure
-
純粋関数 (副作用がなく、一貫性がある関数) ですよ、という宣言 (関数の修飾子) です。
コンパイラーがチェックしてくれます;
関数の外のスコープにある変更可能な変数へアクセスしたり、 pure でない関数を呼んだりすると、コンパイルエラーになります。
I/O API などはもちろん非 pure なので呼び出せません。ただなぜか引数値を変更してもコンパイルエラーにならないため、
きちんと純粋性を表現するには、関数に対する pure 修飾だけでなく、参照型引数に対する const 修飾が必要です。
Effective C++ 『可能ならいつでも const を使おう』は D 言語でも健在です。純粋関数は「『状態』をもたず、つくらず、もちこませず」の原則を現実のものにします。
- 副作用がないので、バグがあったとしても戻り値がおかしくなるか例外が飛ぶだけです。『状態』をおかしくすることはありません。
- 一貫性があるので、「これこれこういうときだけ再現する」といった状態依存バグが起きません。
- 複数のスレッドから同時に実行できます。排他制御は要りません。
- 単体テストは引数と戻り値の関係だけ検証すれば済みます。
(副作用のある関数の場合、事後状態を検証する必要があります。一貫性のない関数の場合、事前状態による振る舞いの違いを検証する必要があります。これらは本当に骨が折れます。。)
pure だらけのコードは気持ちいい!
# D 言語でなくても関数の純粋性には配慮すべきですね! - ■ nothrow
-
例外 (Exception) を投げませんよ、という宣言です。
ただ残念ながら、 nothrow によって得られる安心は限定的です;
Java の throws と同様、 Exception 以外にも投げられうる Throwable があり、
コンパイラーがチェックしてくれるのは Exception だけです。 - ■ import
-
最初の行ではタプルを使うために std.typecons をインポートしています。
一般にインポートには 2 種類あります。
- 機能を取り込むために必要なインポート (C/include, Python/import, Ruby/require など)
- 名前空間を省略するためだけのインポート (Java/import, C#/using など)
D 言語のインポートは 1. にあたり、機能を取り込むために必要です。
ところで...
上のサンプルコードでは、名前空間修飾せずにシンボル tuple を使っています。
「グローバル名前空間が汚されるのか…」と私も最初は恐怖しました。が、
実際にはシンボルは名前空間 std.typecons 配下にインポートされており、
名前が競合していないシンボルに限り 名前空間を省略してアクセスできます。 - ■ UFCS (Uniform Function Call Syntax)
-
D 言語では、 30.gcd(75) は gcd(30, 75) と同じ結果になります。
すなわち、関数は第 1 引数の型の値に対してメソッドの構文で呼び出すことができます。
C# の拡張メソッドがすべての関数に適用されるイメージですね。C#/LINQ によるシーケンス操作や jQuery による DOM 操作をしたことがあれば分かると思うのですが、
メソッドチェインって気持ちいいんです!import std.algorithm; import std.array; import std.stdio; void main() { [-3, 7, -2, 6, -8, 4, 6] .filter!(x => x > 0) .map!(x => x * x) .array.sort.uniq.writeln; // 標準出力に [16, 36, 49] と出力されます。 // filter, map, array, sort, uniq, writeln はいずれもメンバーでない関数です。 // 関数呼び出しのカッコは引数がなければ省略できるので省略しています。 // ! (テンプレート引数) やラムダ式 (x => x > 0) は未紹介ですが、全体の雰囲気を感じていただければ。 }
ぜんぜん紹介できてませんけど
例が簡単すぎて出てきてませんが(汗)、
D 言語はオブジェクト指向プログラミングの機能をサポートしています。
C++, Java, C# のいいとこどりな感じなのですが、長くなりそうなのでまた別の機会に。
それはそれとして、もう少しだけ、不安や恐怖を拭い去る機能を紹介しましょう。
- ■ const と immutable
-
- const 修飾された型の変数は不変です。
C++ の const より「深い」不変性を保証します; クラスや構造体であれば、メンバーも、メンバーのメンバーも、 … 、変更できません。
ただし、同じインスタンスを参照する別の変数から変更される可能性はあります。 - immutable 修飾された型の変数は不変です。
const とは異なり、 immutable な型の変数に immutable でない型のインスタンスを代入することはできません。
初期化したら 2 度と変更できません。
void main() { auto mutable = new Object; const constant = mutable; // OK immutable fakelyImmutable1 = mutable; // コンパイルエラー immutable fakelyImmutable2 = constant; // コンパイルエラー immutable reallyImmutable = new immutable Object; // OK const actuallyImmutable = realImmutable; // OK }
- const 修飾された型の変数は不変です。
- ■ リソースリーク対策
-
- ガベージコレクションがあります。無効にもできます。
- C++ の RAII イディオム が使えます。さらに、D 言語独自の scope 文 を使うとブロックを抜けるときの処理を予約できます。 try-finally も使えますが必要になることはないと思います。
- ■ マルチスレッディング
-
- グローバル変数や静的変数は明示的に共有しない限りスレッドローカルになります。
- 共有変数の排他制御には Java と同じ synchronized ブロックを使えます。
- ■ 契約プログラミング
-
- 私はまだこのパラダイムに触れていないのでよく分かりません。。
D 言語は、スクリプト言語の表現力、コンパイル言語の堅牢性、ネイティブのパフォーマンスを持つ「なんでもあり言語」です。
1 度触れてみてはいかがでしょうか。
リンク