C#3.0のお勉強(無名関数編)

 無名関数周りを試してみた。

// C#3.0 (VisualC#2008ExpressEdition)
using System;
using System.Collections.Generic;

namespace LambdaSample
{
    class Program
    {
        private void Test1()
        {
            Console.WriteLine("lambda で再帰を書いて即評価");

            Func<int, int> fact = null; // 再帰を書くときは予め変数定義しておく。
            int result = (fact = n =>
                n <= 1 ? 1 : n * fact(n - 1))(10); // 10の階乗

            Console.WriteLine(result);
        }

        private void Test2()
        {
            Console.WriteLine("ローカル関数で再帰してみる");

            Func<int, int> fact = n =>
            {
                //「式」ではなく「文」を書くこともできる。
                //「文」の場合 return が必要。

                Func<int, int, int> loop = null;
                return (loop = (m, acc) =>
                    m <= 1 ? acc : loop(m - 1, m * acc))
                    (n, 1);
            };
            int result = fact(10);              // 10の階乗

            Console.WriteLine(result);
        }

        private void Test3()
        {
            Console.WriteLine("レキシカル変数をちゃんともてるのか?");

            Func<Dictionary<string, Func<int>>> makeCounter = () =>
            {
                var i = 0; //レキシカル変数
                return new Dictionary<string, Func<int>>{
                    {"up", () => ++i}
                    ,{"down", () => --i}
                    ,{"show", () => i}
                };
            };

            var cnt1 = makeCounter();
            var cnt2 = makeCounter();

            Console.WriteLine(cnt1["up"]());   // 0 --> 1
            Console.WriteLine(cnt1["up"]());   // 1 --> 2
            Console.WriteLine(cnt1["up"]());   // 2 --> 3
            Console.WriteLine(cnt1["down"]()); // 3 --> 2
            Console.WriteLine(cnt2["show"]()); // cnt2 は変化せず 0
        }

        [STAThread]
        public static void Main(string[] args)
        {
            var p = new Program();
            p.Test1();
            p.Test2();
            p.Test3();
        }
    }
}

 期待通りに動きました。
 でもTest3 の長ったらしい Dictionary 型宣言が二箇所にあるのが気に入らない。そもそも辞書を使ったのは Javascriptのこんなコードからの連想なんだけど・・・。

// javascript
function makeCounter(){
    var i = 0;
    return {
        up:function(){return ++i;}
        ,down:function(){return --i;}
        ,show:function(){return i;}
    };
}

var cnt1 = makeCounter();
cnt1.up();

 javascript の場合、辞書はそのままオブジェクトになる。cnt1["up"]() なんて書かずに cnt1.up() と自然に書ける。

C#3.0には匿名型というのがあるらしいが・・・

 匿名型を使ったらこんな風にかけるかな? makeCounter の返り値が objectなのは微妙だけどこのさい仕方ない。4.0の dynamic で解決するだろう。

// C#3.0

//(略)
        private void Test3_2()
        {
            Func<object> makeCounter = () =>
            {
                var i = 0; //レキシカル変数
                return new
                {
                    up = () => ++i
                    , down = () => --i
                    , show = () => i
                };
            };

            var cnt1 = makeCounter();
            // Console.WriteLine(cnt1.up()); // 3.0では無理だけど
        }
//(略)

 残念ながらこのコードはコンパイル通りません。プロパティup,down,showがエラーになります。「ラムダ式を匿名型プロパティに割り当てることはできません」だそうです。javascriptのようにはいかないんですね。

じゃあ多値は?

 探したけどC#には return で多値を返す lisp のような構文はないようです。

;; Common Lisp
(defun make-counter ()
  (let ((i 0))
    (values (lambda () (incf i))
            (lambda () (decf i))
            (lambda () i))))

;; 多値の受け取り
(multiple-value-bind
      (cnt1-up cnt1-down cnt1-show) (make-counter)
  (print (funcall cnt1-up))
  (print (funcall cnt1-up))
  (print (funcall cnt1-up))
  nil)

 こういうのはできない。C#ではコレクションで返すか、引数にoutをつけて返すしかないようです。
 結局 Test3 は、ああいう書き方しかできないのか。