明けましておめでとうございます. @susisu2413 です.
この記事は OUCC アドベントカレンダー 2014 21日目の記事です. 昨日は @spring_raining 氏による大学のグループ開発でgitを布教する方法でした.
盛んに「関数型で脱アルゴリズム」などと叫ばれる昨今ですが, その真偽はさておき,
処理の単位として関数を用いるのは処理の一般化や再利用をする場合に役に立つことが多いです (たぶん).
関数型っぽい処理としては, 生の JavaScript では Array
のメソッドに map
, filter
, reduce
などがあります
(詳しくは このへん を参照).
Underscore.js とか Lo-Dash でもいいと思います (私はちゃんと使ったことがないのでよく知りません).
ここでは上記のような関数を引数にとるようなメソッド (あるいは関数) を使う場合に, 微妙に不便だったりする事例とその解決法を紹介します.
例えば, 文字列を大文字にしたい場合はこんなかんじで良いですね.
"foo".toUpperCase();
// -> "FOO"
では, 文字列が入っている配列のすべての要素に対して, map
を使って toUpperCase
メソッドを適用して大文字にしたいとしましょう.
["foo", "bar"].map(function (str) {
return str.toUpperCase();
});
// -> ["FOO", "BAR"]
toUpperCase
を map
したいだけなのに, なんだか複雑なことになってしまいます そんなに複雑ではない.
toString
メソッドの本体は String.prototype.toUpperCase
という関数なのですが,
これを引数の文字列を大文字にするように使うには,
String.prototype.toUpperCase.call("foo");
// -> "FOO"
のようにする必要があります.
でも, map
に与えるのは String.prototype.toUpperCase.call
ではいけません.
call
もメソッド (本体は Function.prototype.call
) だからですね.
というわけで結局こうなります.
["foo", "bar"].map(Function.prototype.call.bind(String.prototype.toUpperCase));
// -> ["FOO", "BAR"]
逆に最初より長くなってしまったので, こんな感じで関数を用意しておきましょう.
function method(method) {
return Function.prototype.call.bind(method);
}
こうすれば, サクッと.
["foo", "bar"].map(method(String.prototype.toUpperCase));
// -> ["FOO", "BAR"]
まあ, 結局中身は最初のやつとあまり変わらないんですけどね.
JavaScript のコンストラクタはただの関数なので, コンストラクタとして使う場合は new
演算子を使う必要があります.
function Hello(name) {
this.name = name;
}
Hello.prototype.say = function () {
console.log("Hello, " + this.name);
};
var foo = new Hello("world");
foo.say();
// -> "Hello, world"
これもメソッドと同じようにそのまま map
などに渡すことが出来ません.
自前でコンストラクタの処理を実装する必要があるので, 諦めて仕様書を開いて [[Construct]]
を見ましょう.
function construct(Constructor) {
return function () {
if (typeof Constructor !== "function") {
throw new TypeError(typeof Constructor + " is not a function");
}
var proto = Constructor.prototype;
if (proto === null || typeof proto !== "object") {
proto = Object.prototype;
}
var obj = Object.create(proto);
var result = Constructor.apply(obj, arguments);
if (result === null || typeof result !== "object") {
return obj;
}
else {
return result;
}
};
}
こうすれば以下のようにできるので便利です.
["foo", "bar"].map(construct(Hello)).forEach(method(Hello.prototype.say));
// -> "Hello, foo"
// -> "Hello, bar"
上記のコードはビルトインのコンストラクタなどには使えないぽいので,
function construct(Constructor) {
return function () {
return new (
Function.prototype.bind.apply(
Constructor,
[undefined].concat(Array.prototype.slice.call(arguments))
)
)();
};
}
としたほうが良さそうです.
諦めて (๑❛ᴗ❛๑)♡
var concat = function (x, y) { return x + y; };
["a", "b", "c"].reduce(concat, "");
// -> "abc"
["a", "b", "c"].reduceRight(concat, "");
// -> "cba"
関数型 JavaScript は時折つらい.
は @E_Rubik さんです.