前回のエントリ、AS3でFunction.bind()に引き続き “関数 (function)” を扱ってみます。
今回は関数のカリー化 (currying)についてです。
とその前に、前回のエントリで示した Function.bind() について。
あれは昨今のAS3文化には馴染まないアプローチだったと思います。
特にAS3からFlashの世界に入った方は prototype に拒否反応があったりするかもしれません。
ということでよりAS3文化に馴染んだアプローチを探してみます。
たぶんきっと「関数を借りる」といったこともしないと思うので、
引数束縛だけを使うシチュエーションを考えます。
(もし、関数拝借を普通によくやってるFlasherの方がいたらごめんなさい。。)
* 引数束縛して部分適用: Partial Application
(ES5の Function#bind とは別モノです)
[as]
package {
import flash.display.Sprite;
… なんかたくさん import
public class Main extends Sprite {
…. コンストラクタとかいろいろ
// なんらかの処理を行う
private function processing():void {
var sum1:Function = bind(sum, 1); // 1引数を束縛して部分適用
var sum2:Function = bind(sum, 4, 5); // 2引数を束縛して部分適用
trace(sum1(2, 3)); // => 6
trace(sum2(6)); // => 15
}
// 3つの整数引数を取り、それらの和を返す
private function sum(a:int, b:int, c:int):int {
return a + b + c;
}
// 引数を束縛して部分適用 (Partial Application)
private function bind(f:Function, …args):Function {
return function(…rest):* {
return f.apply(null, args.concat(rest));
};
}
}
}
[/as]
…(rest) はおそろしい子だと前から思ってた。
Adobe ActionScript 3.0 * 関数パラメータ
部分適用がわかったところで、次は関数のカリー化を扱います。
関数のカリー化 (currying)
関数のカリー化とは、
「(X, Y) -> Z という関数をX -> (Y -> Z) という関数に変換すること」
部分適用とカリー化はどうやらちゃんと区別されるようです。
Currying != Generalized Partial Application?! | Lambda the Ultimate
また、関数型言語のHaskellでは自動的に関数がカリー化されます。
で、AS3ではどうやって実現するのか。
検索してみると既に試みてる方がいました。
ActionScript3でカリー化(引数束縛) – #87にっき
ただ、strictモードをoffにしないとコンパイルが通らないのはキビしいです。。
(Call to a possibly undefined method ~ と怒られます)
今のAS3文化だとprototypeもstrictモードoffも受け入れられないと思うので、
prototype不使用かつstrictモードonで関数をカリー化させる方法を考えます。
実は既にStack OverflowにもAS3でのカリー化が例示されていました。
Flex: implementing classic curry function in actionscript? – Stack Overflow
* 関数のカリー化: Function Currying
[as]
// クラス名は FunctionUtils
public static function curry(func:Function, … args:Array):* {
var arity:int = func.length;
var currying:Function = function(func:Function, arity:int, args:Array):* {
return function(… moreArgs:Array):* {
if(moreArgs.length + args.length < arity) {
return currying(func, arity, args.concat(moreArgs));
}
return func.apply(this, args.concat(moreArgs));
}
}
return currying(func, arity, args);
}
[/as]
これは純粋なカリー化ではなく、たぶん"複数回の部分適用"になるんでしょうか。
この要件ならもっと綺麗に書けるような気がします。
* 改修例
[as]
// クラス名は FunctionUtils
public static function curry(f:Function, ...rest):Function {
function currying(args:Array):* {
if(args.length >= f.length) {
return f.apply(null, args);
}
return function(…more):* {
return currying(args.concat(more));
};
}
return currying(rest);
}
[/as]
条件をクロージャの外に持ってくるだけでシンプルに書けます。
もし純粋なカリー化をする場合、…(rest) は普通の配列として扱えるので、
引数をひとつずつ取って回してあげれば簡単にできるかと。
ただ、Function.apply() の挙動がJavaScriptと違って引数の数に厳格なので、
引数の数を合わせないと ArgumentError になってしまいます。。
でもとりあえずはstrictモードonで良い感じに動くんじゃないでしょうか。
* 使い方
[as]
// 3つの整数引数を取り、それらの和を返す
function sum(a:int, b:int, c:int):int {
return a + b + c;
}
var curriedSum:Function = FunctionUtils.curry(sum);
trace(curriedSum(1, 2, 3));
// => 6
trace(curriedSum(1)(2, 3));
// => 6
trace(curriedSum(1, 2)(3));
// => 6
trace(curriedSum(1)(2)(3));
// => 6
trace(curriedSum()(1)()()(2)()()()(3));
// => 6
[/as]
ちょっとおもしろい。
* これをいったいどこで使うのか
コードを見てぱっと気付いた方とそうでない方とに分かれると思うのですが、
部分適用が可能ということは、事前に引数のリストを用意しておけるということです。
つまり、複数引数を持つメソッドにおいて一部の引数を固定しておき、
残りの引数を変化させて何度も呼び出すといったシチュエーションで利用できます。
Flash PlayerのAPIは引数がたくさんあるものも多いので利用できるかもしれません。
wonderfl にもサンプルを投稿しておいたので興味があれば。
Function Currying – wonderfl build flash online