新しいスタイル-再帰プログラミング
再帰
関数プログラミングでは再帰処理がよく使われているので、ちょっと真似してみましょう。
次に示す例は、再帰的に条件式を組み立てています。
public void MainFunction() { var cond1 = this.CreateConditionChain( new Func<int, bool>[] { ( (x) => x > 0 ), ( (x) => x <= 10 ), ( (x) => x%2==0 ) } ); int a = 6; int b = 11; if ( cond1( a ) ) Console.WriteLine( "a !" ); if ( cond1( b ) ) Console.WriteLine( "b !" ); var cond2 = this.CreateConditionChain( new Func<string, bool>[] { ( (x) => x.Length >= 5 ), ( (x) => x.IndexOf( "a" ) != -1 ) } ); List<string> list = new List<string>{ "vb", "c sharp", "hello", "application", "LISP", "lambda" }; var result = from x in list where cond2( x ) select x.ToUpper(); result.ToList().ForEach( Console.WriteLine ); // 出力 } private Func<T, bool> CreateConditionChain<T>( params Func<T,bool>[] fns ) { LinkedList<Func<T, bool>> fnlist = new LinkedList<Func<T, bool>>( fns ); return this.Foo( fnlist.First ); } private Func<T, bool> Foo<T>( LinkedListNode<Func<T, bool>> fn ) { if ( fn.Next == null ) return fn.Value; var chain = this.Foo( fn.Next ); return ( (x) => fn.Value( x ) && chain( x ) ); }
a ! C SHARP APPLICATION LAMBDA
cond1には、「0より大きい」かつ「10以下」かつ「偶数である」かどうかを判断する関数が格納されます。 cond2では文字列を対象に、「長さが5以上」かつ「aという文字列を含む」かどうかを判断する関数を格納しています。せっかくなのでこちらは結果の取得をLINQで書いてみました。
CreateConditionChain関数は補助的なもので、メインは3行からなるFoo関数です。処理中に自身を呼び出すことによって、条件判断関数を連結させています。これもクロージャによって関数の連なりを保持しています。返されるものも関数ですので、これをまたCreateConditionChainに渡して別の条件を後から付け加えることもできます。
さて、CreateConditionChain関数はせっかく可変長引数を受け取るように宣言されているのですから、次のように渡してしまいましょう。
var cond1 = this.CreateConditionChain(
((x) => x > 0), ((x) => x <= 10), ((x) => x%2==0) );
ちょっとすっきりしました。しかし、このように渡すとコンパイルできません。渡されたラムダ式だけを見ても、コンパイラは引数xの型を推論できないことが原因です。そんな時は型を教えてあげます。
var cond1 = this.CreateConditionChain<int>( ((x) => x > 0), ((x) => x <= 10), ((x) => x%2==0) );
また、もし判断に使う関数があらかじめ用意されていたり、次のような汎用的な関数がある場合はもっとスマートに書くことができます。
Func<int, bool> evenp = ( (x) => x%2== 0 ); Func<int, bool> plusp = ( (x) => x > 0 );
public void MainFunction2() { var cond1 = this.CreateConditionChain( evenp, plusp ); int a = 3; int b = 4; Console.WriteLine( "{0} : {1}", cond1( a ), cond1( b ) ); }
False : True
コーディング時に複雑な条件式を簡潔に書けるようになるのはもちろんですが、条件式を動的に組み替えることも可能です。また、条件判断を行う際に式の詳細まで知る必要がない場合、複雑な条件式を書いてプログラムの可読性を低下させるのではなく、生成過程を隠蔽して生成された判断関数だけを返すようにもできます。 イメージとしてはGoFのFactory MethodやBuilderパターンと言ったところでしょうか。
最適化が施されていない再帰処理にはオーバーヘッドがありますし、慣れていないと正直扱いにくいものだとは思いますが、適所で使えばなかなか強力なのではないかと思います。
