はじめに
q.jsのソースコード中で見つけた、uncurryThis
という関数。
// Attempt to make generics safe in the face of downstream
// modifications.
// There is no situation where this is necessary.
// If you need a security guarantee, these primordials need to be
// deeply frozen anyway, and if you don’t need a security guarantee,
// this is just plain paranoid.
// However, this **might** have the nice side-effect of reducing the size of
// the minified code by reducing x.call() to merely x()
// See Mark Miller’s explanation of what this does.
// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
var call = Function.call;
function uncurryThis(f) {
return function () {
return call.apply(f, arguments);
};
}
// This is equivalent, but slower:
// uncurryThis = Function_bind.bind(Function_bind.call);
// http://jsperf.com/uncurrythis
var array_slice = uncurryThis(Array.prototype.slice);
コメント部分を訳してみる。
後の改変に対して安全に汎用化することを試みる。
これがなくてはならない場所はない。
安全性を担保する必要があるのなら、これらの基本原理はどこか深くに凍結する必要があり、もし安全性を担保する必要がないのなら、これは単なる妄想である。
しかしながらこれはx.call()
を単にx()
にすることによってミニファイされたコードのサイズを縮小する良い副作用をもたらす。
これらが何をするのかはMark Millerの説明を参照せよ。
翻訳がいまいちなせいもおそらくあると思うけど、今一つ理解できない。
2つ目のコメント部分のコードはほぼ Mark MillerさんのドキュメントにあるuncurryThis
のコードである。q.jsのuncurryThis
はこのコメントに書かれてあるコードのパフォーマンス改良版だそうだ。
ただこのuncurryThis()
って必要なの?と思う。下のように単なるエイリアスで済むのでないかと私のようなJSビギナーだと思うよね。
var array_slice = Array.prototype.slice.call;
何か利点があるはずだと思いMark Millerさんのドキュメントを読むとずばり書いてあった。そのことを書く前にMark MillerさんのuncurryThis
を見てみることにする。
Mark MillerさんのuncurryThis
Mark MillerさんのuncurryThis
のコードを引用すると以下の通りであった。
conventions:safe_meta_programming [ES Wiki]
var addAll;
(function() {
"use strict";
var bind = Function.prototype.bind;
var uncurryThis = bind.bind(bind.call);
// not needed for this example, but often useful
var applyFn = uncurryThis(bind.apply);
var sliceFn = uncurryThis([].slice);
addAll = function(var_args) {
var args = sliceFn(arguments, 0);
//...as above
};
})();
このuncurryThis
のコードの実装は非常に興味深いものとなっている。uncurryThis
そのものは下記の2行である。
var bind = Function.prototype.bind;
var uncurryThis = bind.bind(bind.call);
bind
変数の中身でuncurryThis
変数の中身を置き換えると下のコードとなる。
var uncurryThis = Function.prototype.bind.bind(Function.prototype.bind.call);
Function.prototype.bind
のthis
にFunction.prototype.bind.call
を束縛している。つまりこれは
// 擬似的な表現であるこということに注意すること。あくまでイメージ。
var uncurryThis = Function.prototype.bind.call.bind;
ということである。
ここでのポイントはFunction.prototype.bind.call.bind
のbind
は実際にはFunction.prototype.bind.call.bind
メソッドが呼ばれるのではなく、Function.prototype.bind
メソッドが呼ばれるということである。
続いてuncurryThis
を引数[].slice
で呼び出してみると、これは
// 擬似的な表現であるこということに注意すること。
//
uncurryThis([].slice);
//↓
Function.prototype.bind.call.bind([].slice);
//↓Function.prototype.bind部分がthisなので、そこが[].sliceに置き換わるイメージ
[].slice.call;// ただしこのcallはFunction.prototype.bind.callである。
となる。注意点としては[].slice.call
のcall
は[].slice
のcall
メソッドではなく、Function.prototype.bind.call
メソッドであるということだ。
実に巧妙だが、ほかの書き方もできそうな気がする。おそらくこれが一番短い実装とかなんだろうな。q.js版のほうはパフォーマンス改良版となるのだろうか。
(それにしてもこのあたりを理解するときの脳の使われ方がC++のtemplateを使ったメタ関数を理解するときとてもよく似ている気がする。)
uncurryThisの必要性
Safe Meta Programmingを読むと、uncurryThis
がなぜ必要なのかがわかる。uncurryThis
の必要性は、ビルトイン・オブジェクトのメソッドを確実に呼び出すことができるという点にある。この点が「Safe」なのであった。
例えば[].slice.call
のcall
が改変・削除されていても正しく動作させることができる。
具体的にはuncurryThis
を使用すれば、Function.prototype.call
が確実に呼び出されることが保証される。(これもまたFunction.prototype.call
が正常に動作することが前提であるのだが。それとuncurryThis
の実際のコードではFunction.prototype.bind.call
となっているが、まあそれはそれとして)
さらにMark MillerさんのuncurryThis
はFunction.prototype.bind.call.bind
が改変されることを想定して、Function.prototype.bind
も確実に呼ぶようになっている。
私が最初のほうで書いた
var array_slice = Array.prototype.slice.call;
だと、あとでどこかでcall
が改変されると動作しなくなってしまうのだ。
さらにその副作用として、コードサイズの縮小があるわけだ。q.jsのコメントの意味がようやく理解できた。
意図したように動作するように、(ビルトイン)オブジェクトの動作を固定するのである。
そもそもuncurryThisとは何か
Axel Rauschmayerさんのドキュメントにあるリンク「Uncurrying this
in JavaScript」を読むと以下のことが書いてあった。以下引用し翻訳すると、
Uncurrying thisとは与えられたシグネチャ付きのメソッドを
obj.foo(arg1, arg2)
シグネチャを持つ関数に変換することである。
foo(obj, arg1, arg2)
と書いてあった。
しかしカリー化の逆という意味ではなさそうだな。なぜこれをuncurryThis
と名付けたのかね。。
Brendan EichさんのuncurryThis
Axel Rauschmayerさんのドキュメントには、かのBrendan EichさんのuncurryThisに関してのTweetへのリンクがある。
TweetされたuncurryThis
コードを引用しておく。
https://twitter.com/BrendanEich/status/471391353536921600
Function.prototype.uncurryThis=function(){var f = this; return function(){return f.call.apply(f,arguments)}}
最後に
JSはいつでもオブジェクトの構造や内容が改変できてしまうので、そのことも考慮しなくてはいけないのだということがよく分かった。それが頭に入っていないと今回のuncurryThis
も理解できないよね。