以前D言語を書いている時に、任意の関数をカリー化したくなって、次のようなテンプレートを書きました。
// カリー化用テンプレート
template curry(alias func){
import std.algorithm, std.conv, std.range, std.traits;
alias argsintuple = ParameterTypeTuple!func;
immutable lambdaStr = (lamArgs =>
(temp){
foreach(i, e; argsintuple)
temp ~= "(" ~ e.stringof ~ " " ~ lamArgs[i] ~ ") => ";
return temp ~ "func(" ~ lamArgs.join(", ") ~ ")";
}("")
)(argsintuple.length.iota.map!(i => "arg" ~ i.to!string));
enum curry = mixin(lambdaStr);
}
それで、先ほどTypeScriptを書いている時に同様のカリー化する関数がほしいなぁと思って上のテンプレートをTypeScriptで書いてみました。
Dでの実装もTypeScriptでの実装も単純で、ざっくり言うと
引数の数だけ "(arg0) => (arg1) => (arg2) => .... (argN) => "
という文字列をつくり、最後に "func(" + args.join(", ") + ")"
としてるだけです(引数の数だけ展開して、一引数のラムダ式をつくり、それをネストさせているだけです)。
僕はTypeScript(JavaScriptも)まったくの素人ゆえ、これがTypeScript(/JavaScript)として妥当なアプローチなのかはわかりませんが...
とりあえず、TypeScriptに上のテンプレートを書き換えたものがこちらです:
function isFunction(obj: any): boolean {
return typeof(obj) == "function";
}
function iota(cnt, init = 0, inc = 1) {
var ret = [];
for (var i = 0, j = init; j < cnt; i++, (j = j + inc))
ret[i] = j;
return ret;
}
function curry(func: any) {
if (!isFunction(func)) {
console.log("Fatal Error. An invalid value was given. It was not a function");
return;
}
const lamdbdaStr = (lamArgs =>
((temp) => {
for (var i = 0; i < lamArgs.length; i++)
temp += "(" + lamArgs[i] + ") => ";
return temp + "func(" + lamArgs.join(", ") + ")";
})("")
)(iota(func.length).map(x => "arg" + x));
return eval(lamdbdaStr);
}
D言語ではカリー化する対象が関数(もしくはdelegate/function)であるかはコンパイル時に判定されるので変な引数が渡されてもコンパイル時に弾けますが、TypeScriptではそうはいかないので(ただ単に僕がしらないだけで、TypeScriptでオブジェクトが関数である
ということを型で表せるのであれば、トランスパイル時に弾けると思います)適当な判定関数を噛ませています。
試しに次のコードを動かしてみると、きちんとカリー化されていることが確認できます。
function add3(a: number, b: number, c: number) {
return a + b + c;
}
console.log("isFunction(curry(add3)) : " + isFunction(curry(add3)));//true
console.log("isFunction(curry(add3)(1)) : " + isFunction(curry(add3)(1)));//true
console.log("isFunction(curry(add3)(1)(2)) : " + isFunction(curry(add3)(1)(2)));//true
console.log("isFunction(curry(add3)(1)(2)(3)) : " + isFunction(curry(add3)(1)(2)(3)));//false
console.log(curry(add3)(1)(2)(3) == 6);