db4oを使ってみる

Monoミーティングで話題に挙がったdb4o(オブジェクトデータベース)のチュートリアルを見て、これはNemerleで書くと楽チンだなぁ、とか考えてしまい、つい、手を出してしまいました。こんなことしているから、GWが計画通りに進まないんですよね。(^^;

あと、Nemerleの文法を解説していますので、その辺に興味のある方もどうぞ。

using System;
using System.IO;
using Nemerle.Utility;
using com.db4o;

[Record] 
public class Pilot {
    [Accessor] _name : string;
    [Accessor] mutable _points : int;

    public AddPoints (points : int) : void {
        _points += points;
    }

    public override ToString() : string {
        $"$_name/$_points";
    }
}

def dbname = "mydb";

when (File.Exists (dbname)) File.Delete(dbname);

mutable db = null;
try {
    db = Db4o.OpenFile ("mydb");
    db.Set (Pilot ("Michael Schumacher", 100));
    db.Set (Pilot ("Rubens Barrichello", 99));

    def pilots = db.Query.[Pilot] (pilot => pilot.Points > 99 && pilot.Points < 199 || 
            pilot.Name == "Rubens Barrichello"); 

    foreach (pilot in pilots) Console.WriteLine (pilot);
}
finally { 
    _ = db.Close ();
}

/* 結果
Rubens Barrichello/99
Michael Schumacher/100
 */

予想通り、すっきり書けます。Nemerleを知らないと???なところがあると思いますので、ちょっと解説します。

[Record] 

まずは、いきなりのRecord属性。Nemerleコンパイラがサポートする属性で、これがついていると全フィールドを引数に取るコンストラクタを自動生成してくれます。Pilotクラスの場合、_nameと_pointsを持ちますので、

public this (_name : string, _points : int) {
    this._name = _name;
    this._points = _points;
}

このようなコンストラクタが生成されます。ちなみにNemerleのコンストラクタはthisとなっています。

[Accessor] _name : string;

Record属性が終わったと思ったら、すぐさま見知らぬ属性が出てきました。こっちはAccessorの名前から想像がつくと思いますが、この属性をつけるとプロパティを自動生成してくれます。

public Name : string {
    get { _name; }
}

こんなコードになります。便利ですよね。

[Accessor] mutable _points : int;

mutable・・・なんか、知らないキーワードばっかしですね。Nemerle関数型言語とのハイブリッドというのはご存知だと思いますが、関数型言語って基本的に変数の値を書き換えることができないことになっています。Nemerleでも変数はデフォルトでC#で言うところのreadonlyが付けられています。なので、値を変更したい変数にはmutableを付けなければなりません。_pointsにもAccessor属性が付いているので、

public Points : int {
    get { _points; }
}

プロパティが作成されます。Pointsは書き込み可能なのでsetterも用意されています。中々賢いですよね。これは間違いでした。setterが必要な場合は、

[Accesser(flags=WantSetter)] mutable _points : int;

とする必要があります。考えてみれば、フィールドが書き込み可能だからといって必ずしもsetterを公開するとは限りませんよね。(^^;

ところで、プロパティのネーミングですが、

_first_name

のように_で区切っているところを大文字にして、

FirstName

のように変換します。

次はちょっと飛んで、

$"$_name/$_points";

$付きの文字列ですが、文字列中の$変数(式や関数も書ける)を置き換えてくれるマクロです。上記の場合、

String.Format ("{0}/{1}", _name, _points)

これとほぼ同じ内容となります。(実際のコードはStringBuilderになりますが)
あと、気づいている人がいるかもしれませんが、Nemerleでは一番最後に評価された値が戻り値になります。なのでreturnはありません。この辺はRubyと一緒かな。

when (File.Exists (dbname)) File.Delete(dbname);

コードを見れば、条件分岐だと分かるかもしれませんね。なぜ、ifでなくwhenなんだ?と思う人もいるかもしれません。実は、Nemerleにもifはあります。しかし、Nemerleのifは必ずelseが必要となります。なのでelseが不要の場合は、whenを使うことになります。

で、また、ちょっと飛んで、

db.Set (Pilot ("Michael Schumacher", 100));

Pilotクラスを生成していますが、newキーワードはありません。Python等と同じですね。

def pilots = db.Query.[Pilot] (pilot => pilot.Points > 99 && pilot.Points < 199 || 
        pilot.Name == "Rubens Barrichello"); 

ここで、まず気になるのは、

db.Query.[Pilot]

C#プログラマには配列のようにも見えますが、

db.Query<Pilot>

genericsだったりします。C++/C#で使っている<>はコンパイラの解析が遅くなるのでこっちにしたらしいです。Eiffelとかと一緒なので、違和感が無い人もいるかもしれませんね。ただ、配列を同じ記号を使った代償で.(ピリオド)が必要になっているところが嫌らしいです。(一応、フォローしておくと、ネットでアンケートとって一番マシだったのがコレなんですよ)

ラムダ式は昨日紹介しているので飛ばして、

_ = db.Close ();

よく分からない_に代入していますね。これは関数の戻り値を無視したい場合に使います。すでに紹介したとおり、Nemerleでは最後に評価した値を戻り値とします。もし、voidの関数内で呼び出した関数に戻り値があったとします。これはC#に例えると、void関数内で「return 値;」と書いたことに相当します。もちろん、コンパイルエラーになるので、_に代入して戻り値を無効にする必要があります。

ってことで、簡単なNemerleコードの解説でした。