はじめてのNemerleその3

予定通りGenericsについて紹介しようと思ったのですが、基本的にC#2.0と一緒なので改めて説明することはほとんどなかったりします。(^^;
以下、手抜きvector

using System;
using System.Collections;

class vector<'t> where 't : IComparable
{
    private mutable _initial : int;
    private mutable _arr : array<'t>;
    private mutable _capacity : int;
    private mutable _size : int;

    public this(n : int) 
    {
        if (n > 0)
          _initial <- n
        else
          _initial <- 16;

        _arr <- array(_initial);
        _capacity <- n;
        _size <- 0;
    }

    public size() : int
    {
        _size;
    }

    public empty() : bool
    {
        _size == 0;
    }

    public push_back(x : 't) : void
    {
        when (_size >= _capacity) {
            _capacity <- _capacity * 2;
            mutable a <- array(_capacity);
            Array.Copy(_arr, a, _arr.Length);
            _arr <-> a;
        };
        _arr[_size] <- x;
        ++_size;
    }

    public get(index : int) : 't
    {
        _arr[index];
    }

    public clear() : void
    {
        _arr <- array(_initial);
        _capacity <- _initial;
        _size <- 0;
    }
}

module M
{
    Main() : void
    {
        def vi = vector(5);
        for (mutable i <- 0; i < 10; ++i) 
            vi.push_back(i * 100);
        // vi.push_back(5.0);  // NG

        def vf = vector(5);
        for (mutable i <- 0; i < 10; ++i)
            vf.push_back(0.01f * (i:>float));
        // vf.push_back(5);  // NG
    }
}

class vector<'t>のように'(シングルクォート)が付いている以外に違いはありません。ただ、インスタンス作成がちょっと注意です。

def vi = vector(5);

C#ですとvector(5)となりますが、Nemerleの場合は型を指定しません。では、どのようにしてint型だと分かるのでしょうか。実はvi.push_back(5)でint型の値を渡しているところから推論されます。もちろん、型推論可能なメソッドを最後まで呼ばないとエラーになってしまいますが、普通はそんなことは無いと思います。また、一旦型が決まったら当然のごとく異なる型の値は渡せません。(サンプルのNG箇所を参照)

Nemerleについて特徴的な機能を抜き出して紹介してきましたが、きりがなくなってきたのでタプルとマクロを紹介して終わりとします。

まずはタプル。タプルとは値を変更できないリストだと思って下さい。タプルを使うと関数の戻り値として複数の値を返せたり、変数の入れ替えが直感的に出来たりします。

using System;

module M
{
    // タプルを返す関数
    Method() : int * int * int
    {
        (0, 128, 255)
    }

    Main() : void
    {
        // タプルを作成
        def orange = (255, 165, 0);

        // タプルを分解
        def (r, g, b) = orange;
        Console.WriteLine("{0}, {1}, {2}", r, g, b);

        // タプルを使った入れ替え
        def (b, g, r) = (r, g, b);
        Console.WriteLine("{0}, {1}, {2}", r, g, b);

        // 関数の戻り値にも使える
        def (r, g, b) = Method();
        Console.WriteLine("{0}, {1}, {2}", r, g, b);
    }
}

昨日の高階関数で出てきたint * int * intのような表記は実はタプルを表していたのでした。スクリプト言語みたいな感じです。これでスライスが使えれば言うこと無いのですけど。(調査不足かな?)

最後にマクロです。マクロというとC言語のマクロを想像していまい、あんま大したことなさそうな気がしますが、NemerleのマクロはLispのそれに近い能力を持っていてかなり強力です。C#にlockという構文がありますが、これは次のような記述のシンタックスシュガーです。

Monitor.Enter(x);
try {
  // xに対する操作
}
finally {
  Monitor.Exit(x);
}

// C#ではlock(x) {...}と書ける

これをNemerleのマクロで実現すると次のようになります。

macro lock(x, body)
{
  <[
    Monitor.Enter($x);
    try
      $body
    finally
      Monitor.Exit($x);
  ]>
}

使い方(syntaxなし)

lock(x, ... /* xに対する操作 */)

これだけだと、Cのマクロと変わりませんね。もちろんNemerleのマクロはこれだけではなく、独自の構文を作ることも出来るのです。先ほどのlockマクロを修正します。

macro lock(x, body)
syntax("lock", "(", x, ")", body)
{
  <[
    Monitor.Enter($x);
    try
      $body
    finally
      Monitor.Exit($x);
  ]>
}

syntaxを追加しました。これでlockは言語の構文と同じように使えます。
使い方(syntaxあり)

lock(x) {
  // xに対する操作
}

実はNemerleでは多くの構文がマクロで実現されています。if/while/for/foreachなど、みんなマクロです。つまり、Nemerleでは言語拡張を待たずとも独自に構文を追加することが出来るのです。(C#のusing{...}と同等の構文すらマクロで実現されています)

駆け足で機能を紹介しましたが、どうでしたでしょうか。個人的にはNemerleはとても興味深い言語です。ただ、現在のバージョンが0.12でまだまだ言語仕様が変更される可能性も大きいです。実際に使って行くにはまだ早いですが、今後を期待してみていきたいと思います。