JavaScript

uncurryThis

More than 3 years have passed since last update.

はじめに

q.jsのソースコード中で見つけた、uncurryThisという関数。

q.js 250行-270行
// 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.bindthisFunction.prototype.bind.callを束縛している。つまりこれは

// 擬似的な表現であるこということに注意すること。あくまでイメージ。
var uncurryThis = Function.prototype.bind.call.bind;

ということである。
ここでのポイントはFunction.prototype.bind.call.bindbindは実際には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.callcall[].slicecallメソッドではなく、Function.prototype.bind.callメソッドであるということだ。
実に巧妙だが、ほかの書き方もできそうな気がする。おそらくこれが一番短い実装とかなんだろうな。q.js版のほうはパフォーマンス改良版となるのだろうか。

(それにしてもこのあたりを理解するときの脳の使われ方がC++のtemplateを使ったメタ関数を理解するときとてもよく似ている気がする。)

uncurryThisの必要性

Safe Meta Programmingを読むと、uncurryThisがなぜ必要なのかがわかる。uncurryThisの必要性は、ビルトイン・オブジェクトのメソッドを確実に呼び出すことができるという点にある。この点が「Safe」なのであった。

例えば[].slice.callcallが改変・削除されていても正しく動作させることができる。

具体的にはuncurryThisを使用すれば、Function.prototype.callが確実に呼び出されることが保証される。(これもまたFunction.prototype.callが正常に動作することが前提であるのだが。それとuncurryThisの実際のコードではFunction.prototype.bind.callとなっているが、まあそれはそれとして)
さらにMark MillerさんのuncurryThisFunction.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も理解できないよね。