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 は、ああいう書き方しかできないのか。