C#の型推論は怠けすぎ

2010-09-29(via やねうらおさんとこ)

C#はバランスと取れた良い言語ですが、あえて欠点を挙げると型推論がイマイチですよね。

using System;
using System.Collections.Generic;

module M
{
    static Main() : void 
    {
        // C#のvarに近いが初期化は必須ではない
        // mutable x = null;もOK。この場合、代入は参照型のみになる
        mutable x;

        try {
            // 適当なコード
            x = Dictionary.[int, string]();
            x[10] = "ten";
        }
        catch {
            | x is Exception => Console.WriteLine(x);
        }
        finally 
        {
            match (x)
            {
                | y is IDisposable => y.Dispose()
                | _ => ();
            }
        }
    }
}

C#2.0の頃のNemerleでもこの程度は推論してくれます。・・・と、久々にNemerleのコードを書いてみました。

久々に

Nemerlesvnを覗いてみたら今月、0.9.4が出るような記述がありました。1年半ぶりでしょうか。(^^;
ほとんどが内部的なリファクタリングですが、新機能としてはC#3.0のAuto Propertyが実装されているみたいです。個人的にはmutableキーワードをvarにして、早く1.0をリリースして欲しいんだけどなぁ。

C#3.0で型推論が追加されましたが、varは代入時にしか使えないし、ラムダ式をvar変数に代入しようとするとエラーになるので、

using System;
using System.Linq;
using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static int Fibonacci(int n)
        {
            Func<int, int, int, int> fib = null;
            fib = (x, y, z) =>
            {
                if (x == 0) return y;
                return fib(x - 1, y + z, y);
            };
            return fib(n - 1, 1, 0);
        }
        static void Main()
        {
            Console.WriteLine(Fibonacci(10));
        }
    }
}

こんな記述をしなければなりません。ところで上記で使っているようなdelegate変数を宣言しておくことで再帰呼び出し可能にするテクニック(?)が逃げ道っぽくて気になります。

            var fib = (x, y, z) =>
            {
                if (x == 0) return y;
                return fib(x - 1, y + z, y);
            };
            return fib(n - 1, 1, 0);

こう書けて欲しいのですが・・・

Nemerleだと型推論が強力なのでもーちょっと頑張るし、そもそもローカル関数をサポートするので再帰呼び出しのための小細工は不要だったりします。

using System;

module Program 
{
    Fibonacci (n : int) : int
    {
#if true
        mutable fib;
        fib = (x, y, z) =>
        {
            | (0, _, _) => y
            | _ => fib(x - 1, y + z, y)
        }
#else
        // Nemerleは普通にローカル関数を書けるがC#との対比のため
        def fib (x, y, z)
        {
            | (0, _, _) => y
            | _ => fib(x - 1, y + z, y)
        }
#endif
        fib (n - 1, 1, 0)
    }

    Main() : void
    {
        Console.WriteLine (Fibonacci(10));
    }
}

NemerleUnit

http://nemerle.org/NemerleUnit
単体テスト用の構文(?)がサポートされた模様。公式サイトの更新も久々ですね。メーリングリストは活発なのですが、VS用プラグインやら、マクロの実装周りの話ばかりで中々ネタにできません。(^^;

mutable vs var

公式フォーラムの方で再び議論になっています。流れを見ていると今回はあっさりvarが通りそうな雰囲気・・・

Nemerleの中の人もこんなマクロをアップして、これをデフォルトに入れるべきかどうかが問題だ。とか言っていますが、ユーザは皆Goサインを出しています。(^^;

using Nemerle.Compiler;

macro @var (body) 
syntax ("var", body) 
{ 
  match (body) { 
    | <[ $(nm : name) ]> => <[ mutable $(nm : name); ]> 
    | <[ $(id : name) = $init ]> => <[ mutable $(id : name) = $init; ]> 
    | <[ $name = $init ]> => <[ mutable $name = $init; ]> 
    | _ => Message.FatalError ($"incorrect variable definition '$body'"); 
  } 
}

↑ちなみにこれは、varキーワードを有効にするマクロです。

Late Binding Macro

メーリングリストを見ていたら、Late Bindingを行うマクロが投稿されていました。面白そうなので、Booサイトにあったduck typingのサンプルを移植してみました。

using System;
using System.Threading;
using Kitsu.LateBinding;

def CreateInstance (progID : string) {
    def t = Type.GetTypeFromProgID (progID);
    Activator.CreateInstance (t);
}

def ie = CreateInstance ("InternetExplorer.Application");
late {
    ie.Visible = true;
    ie.Navigate2 ("http://nemerle.org/");
    while (ie.Busy:>bool) Thread.Sleep (500);

    def doc = ie.Document;
    Console.WriteLine ("{0} is {1} bytes long.", doc.title, doc.fileSize);
}

変数ieの型はObjectなのに、メソッドやプロパティにアクセスしています。仕掛けはlateブロックで、この中ではLate Binding、つまり、Reflectionを使ったInvoke〜に変換されるのでした。

COM呼び出しなどの場合、便利そうですね。マクロの詳細についてはこちらを見てください。