読者です 読者をやめる 読者になる 読者になる

あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

partial関数は引数の最後に関数を指定できるとネストした時に見やすい

JavaScript

letみたいなものをJavaScriptに実装しようと思っていろいろやってた時に感じたことなんですけど、Pythonとか、Python志向なMochikitなどに導入されているpartial関数は、引数の最後に関数を指定できると、ネストした時にが見やすいと思いました。好みはあると思いますが。

partial関数とは?

ある関数に必要な引数を部分適用できる関数です。例えば、以下のような関数があったとします。

function FromTo(from, to) {
  return from + 'から' + to + 'まで';
}

FromTo('福井', '東京');

// 結果:
// 福井から東京まで

fromという引数とtoという引数を指定することで、◯◯から◯◯までという文字列を出力できる何の実用性もないプログラムです。

この時、partial関数を使用すると、先にfromという引数を部分適用した関数を作成することができます。*1

function FromTo(from, to) {
  return from + 'から' + to + 'まで';
}

var WhereToGoFromFukui = partial(FromTo, '福井');
WhereToGoFromFukui('東京');

// 結果:
// 福井から東京まで

WhereToGoFromFukui('大阪');

// 結果:
// 福井から大阪まで

以上のように使えます。

ちなみに、以下のようにpartial関数の第二引数以降も指定することが可能です。

function HowFromTo(how, from, to) {
  return how + 'で' + from + 'から' + to + 'まで';
}

var WhereToGoByCarFromFukui = partial(HowFromTo, '車', '福井');
WhereToGoByCarFromFukui('東京');

// 結果:
// 車で福井から東京まで

Pythonのpartial関数もMochikitのpartial関数も、引数の最初に部分適用させたい関数を指定します。

partial関数の実装

これを実装したのが以下のコードになります。

function partial() {
  var ap = Array.prototype, _slice = ap.slice, args = _slice.call(arguments);
  return function() {
    return args.shift().apply(this, args.concat(_slice.call(arguments)));
  }
}

partial関数の第一引数が部分適用させたい関数になって、第二引数以降は返す関数の引数と合成します。

もうちょっと高度なものはJohn Resigさんの記事を見ていただければと思います。

partial関数の第n引数(最後の引数)に部分適用させたい関数を指定する場合の実装

通常のpartial関数は第一引数が部分適用させたい関数になりますが、逆に一番最後の引数を部分適用させたい関数にした場合の実装が以下のコードです。

function partial() {
  var ap = Array.prototype,
      _slice = ap.slice, _pop = ap.pop, _concat = ap.concat, args = _slice.call(arguments);
  return function() {
    return _pop.call(arguments).apply(this, _concat.call(_slice.call(arguments), args));
  }
}

さっきのコードの逆になるような感じの実装です。

で、それで何が見やすくなるの?

partial関数をネストする時に、最後の引数を関数とする方が見やすいと思うんです。

第一引数を関数とするpartial関数のサンプル

まずは、第一引数を関数とするパターンのpartial関数を使ったサンプルコードです。

partial(function(a) {
  return partial(function(b) {
    return partial(function(c, d) {
      return a*b*c*d;
    })(2, 5);
  })(10);
})(10);

// 結果:
// 1000

partial関数をネストする時は、partial関数の中にpartial関数を入れていく感じで、仮引数と実引数の位置が遠く、外側から順番に処理を追っていく感じで読むことになります。

最後の引数を関数とするpartial関数のサンプル

次に、最後の引数を関数とするpartial関数を使ったサンプルコードです。

partial(10)(function(a) {
  return partial(10)(function(b) {
    return partial(2, 5)(function(c, d) {
      return a*b*c*d;
    })})});

// 結果:
// 1000

実引数が仮引数より先に出てくるのはちょっと気持ち悪い人もいるかもしれませんが、仮引数と実引数の位置が近いので、「実引数hogeを仮引数fugaに束縛する」みたいに読んでいけば、なんてことはありません。しかも、先程に比べて、ネストしている感覚が少なく、上から順番に関数が流れていくような感じで読むことができます。

もともとlet相当のものを作りたくてコードを書き始めたわけですが、結果的に到達したこれってletに近くね?とも思います。最後が括弧の連続で終わるあたりLispっぽいし。波括弧が挟まるのは面倒ですけど。

まとめ

元々、JavaScriptで無名関数で処理を包んで、すぐに実行する時*2プレースホルダを作って、その中にコーディングする感じが気持ち悪かったし、関数の処理が大きいと仮引数と実引数が見た目的にすごく離れるのがわかりづらかったりするので、こういう書き方もありだなーと思いました。

最後に

最初から、どちらにも対応できた方が良いですね。最後にどちらにも対応したバージョンを。

function partial() {
  var ap = Array.prototype,
      _slice = ap.slice, _shift = ap.shift,
      _pop = ap.pop, _concat = ap.concat,
      args = _slice.call(arguments);
  return function() {
    return (
      (typeof args[0] === 'function') ?
        args.shift().apply(this, args.concat(_slice.call(arguments))) :
      (typeof arguments[arguments.length-1] === 'function') ?
        _pop.call(arguments).apply(this, _concat.call(_slice.call(arguments), args)) :
      new Error();
    );
  }
}

*1:標準ではJavaScriptにpartial関数はありません

*2:(function(x){...})(args)みたいなやつ