昔っからずっと思っておりましたんですが,ばいんどきらい.
bind( plus, _1, 1 );
ほらもうきらいきらいだいっきらい.ばいんどしんぢゃえ.
plus( _1, 1 );
こう書きたい.めがっさ可読性上昇.ほら,こんな書き方ができたら,あなたも今日から初めてみようお手軽
部分評価
plus( _1, 42 );
& カリー化
plus( 42, _1 )( boost::cref( 42 ) );
& 関数合成
plus( plus( _1, 42 ), 42 );
& flip
plus( _2, _1 );
みたいに思いません?え?思わない?しょぼーん.
で,こうするためにわざわざ普通の関数定義とラムダ対応の関数定義を分けて書くのか?当然 No. 普通の関数としての役割とラムダとしての役割を2重に担う形の関数オブジェクトが当然欲しい.
plus( 2, 3 ); // -> 普通に 5 に評価される plus( _1, 1 ); // -> 1引数関数に評価される
ここまでがモチベーション.
問題
上に書いたことは結構前に実装としては検証済みであり,いつでも ready-to-fire.高階関数を受けない関数を上のような構文で書けるように拡張するのはほとんど無問題.
しかし,高階関数を受ける関数に対してこの構文を提供することを考慮すると設計上深刻な問題がある.
今, f という関数オブジェクトがあったと仮定する. f の第1,第2引数は int を受け,第3引数は高階関数 (たとえば2項述語) を受けるとする.この f に対して上記のような拡張を施そうとすると大問題が発生する.
f( 2, 3, greater ); // 通常の関数評価.問題なっしんぐ f( _1, _2, less ); // -> 2項のラムダ関数として評価.問題なっしんぐ
ここまでは無問題.で,通常の関数として f を評価する際に第3項にはラムダ式を指定したいよね.だからラムダ式を指定してみよう.
f( 42, 42, _1 < _2 ); // もぉ大問題っ!!
何が大問題かよく分からないかも知れませんが,これの式の結果がつまり
なのか
- 2項のラムダ式で,そのラムダ式に対する第1引数が _1 という placeholder に代入され,第2引数が _2 という placeholder に代入され,そこで初めて f という関数が実際に評価されるもの
なのか,という問題.
言い換えると,高階関数を受ける項に与えられる lambda expression だけを特別視して外側の引数に束縛しない設計 (前者) を取るか,高階関数を取る取らないに関わらず常に lambda expression が与えられたならそれを外側の引数に束縛する設計 (後者) を取るか,という問題.
前者の設計の得失
まず前者.前者のほうはほとんどおおよその場合において,ユーザにとって直感的な設計.特に普通の関数として使えることが最重点であるならば明らかにこちらを採用するべき.
f( 1, 42, _1 > _2 ); // ほとんどの文脈において「直感的な」結果となる
一方で,関数の項によってラムダ式の扱いが異なる.これは関数の項を対等に俯瞰したときにバランスが悪い.
f( _1, 42, _1 < _2 ); // 第1引数における _1 と第3引数における _1 とは意味が根本的に異っている
また,コールバック引数の部分に指定する lambda expression を f の外側の引数に束縛したいなら,従来どおり bind を使わないといけない.
bind( f, 1, 42, _1 )( _1 < _2 ); // -> f( 1, 42, _1 < _2 )
後者の設計の得失
次に後者.後者は前者と性質が完全に逆転して,
- 全ての項において lambda expression に対する扱いが対等
- 普通の関数評価として lambda expression を指定する場合 unlambda をかけないといけない
f( 1, 42, unlambda( _1 < _2 ) );
個人的な見解
さてどっちが良いのでしょうかね?っていうかそもそも普通の関数評価とラムダ式対応を同じインタフェースでやるのがまずいのかもねん.
もしこのような構文を本当に提供するならば,個人的には前者を採用するべきだと思っている.その理由は3つ.
- 通常の関数評価の構文を最重視するべき
- コールバック引数の部分の束縛を遅延する要求は相対的に小さい
- Boost.MPL の lambda expression の扱いはまさに上記を実現していて,設計として前者を採用している.
問題としてはなんかこんな感じ.どなたか意見きぼんにゅぅ. R・F・C! R・F・C!