SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

言語仕様から読み解くC# 3.0入門

C# 3.0で追加された言語仕様の解説

新しいスタイル-再帰プログラミング

再帰

 関数プログラミングでは再帰処理がよく使われているので、ちょっと真似してみましょう。

 次に示す例は、再帰的に条件式を組み立てています。

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) );

 また、もし判断に使う関数があらかじめ用意されていたり、次のような汎用的な関数がある場合はもっとスマートに書くことができます。

Common Lispを真似て作った汎用的関数の一例
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パターンと言ったところでしょうか。

 最適化が施されていない再帰処理にはオーバーヘッドがありますし、慣れていないと正直扱いにくいものだとは思いますが、適所で使えばなかなか強力なのではないかと思います。

次のページ
新しいスタイル-動的さと不変

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

のぶひさ(ノブヒサ)

1987年10月10日生まれ札幌市在住参加コミュニティ:CLR/Hはてなダイアリー:http://d.hatena.ne.jp/Nobuhisa/ 

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1771 2007/10/30 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー