音の鳴るブログ

鳴らないこともある

JavaScript の関数にデフォルト引数をつける

CoffeeScript だと関数にデフォルト引数をつけられて便利。

madd = (val, mul=1, add=0)-> val * mul + add

madd(10, 2, 3) # 23 (= 10 * 2 + 3)

Python だと辞書で引数を指定したりできて便利感がさらに高い。

def madd(val, mul=1, add=0):
  return val * mul + add

print madd(10, add=20) # 30 (= 10 * 1 + 20)

一方 JavaScript はそういうことが出来ない駄目な言語だという意見が散見される。 まぁ CoffeeScript みたいに書けば良いのだけど、毎回書くのは面倒だし辞書で指定するみたいなことも考えるとゲンナリする。あと jshint で eqeqeq オプション使っていると CoffeeScript のやり方だと怒られるのでもっと面倒なことになる。

// CoffeeScript のやり方
var madd = function(val, mul, add) {
  if (mul == null) { // 怒られる
    mul = 1;
  }
  if (add == null) { // 怒られる
    add = 0;
  }
  return val * mul + add;
};

こんなことだと文化的なプログラム活動を行えず夏を乗り切ることも出来ないと危機を感じて、関数内にコメントでデフォルト引数の情報を書いておくと、勝手にラッパーしてくれるやつを書いた。よく考えたら別にコメントで書く必要はなかったのだけど、プロトタイプに指定した関数を一括で処理したい時などに便利さが出てくる。最低限の機能しかないけど、もっと頑張れば必須引数のチェックとかも出来て良いんじゃないですかね。

https://gist.github.com/mohayonao/5941630

追記
コメントでやるとミニファイできなくなるので駄目ですね。同じ仕組みで定義を渡す格好良いやり方をもうちょっと考えました。

JavaScript の関数にデフォルト引数をつける 2 http://mohayonao.hatenablog.com/entry/2013/07/08/061158

使い方

var madd = defaults_for(function(val, mul, add) {
  // { val=0, mul=1, add=0 }
  return val * mul + add;
});

console.log(madd(10));           // 10 (= 10 * 1 +  0)
console.log(madd(10, 2));        // 20 (= 10 * 2 +  0)
console.log(madd(10, {add:20})); // 30 (= 10 * 1 + 20)


var Int = (function() {
  function Int(n) {
    this.value = n|0;
  }
  Int.prototype.madd = defaults_for(function(mul, add) {
    // { mul=1, add=0 }
    return (this.value * mul + add)|0;
  });
  return Int;
})();

var i = new Int(Math.PI);
console.log(i.madd(2));               // 6 (= 3 * 2 + 0)
console.log(i.madd({mul:3, add:-9})); // 0 (= 3 * 3 - 9)

コード

var isDictionary = function(obj) {
  return obj && obj.constructor === Object;
};

var args_resolution = function(keys, vals, given) {
  var dict, args = vals.slice();
  if (isDictionary(given[given.length - 1])) {
    dict = given.pop();
    for (var key in dict) {
      var index = keys.indexOf(key);
      if (index !== -1) {
        args[index] = dict[key];
      }
    }
  }
  for (var i = 0, imax = Math.min(given.length, args.length); i < imax; ++i) {
    args[i] = given[i];
  }
  if (dict) {
    args.push(dict);
  }
  return args;
};

var defaults_for = function(func) {
  var body = "" + func;
  var def = body.replace(/^\s*function\s*\(.+\)\s*{\s*(?:"use strict";)?\s*\/\/\s*{([^\n]+?)}[\S\s]+$/m, "$1");
  if (def !== body) {
    var origin = func, keys = [], vals = [];
    def = def.split(",");
    for (var i = 0, imax = def.length; i < imax; ++i) {
      var items = def[i].trim().split("=");
      keys.push(items[0].trim());
      vals.push(items.length === 1 ? 0 : eval(items[1]));
    };
    func = function() {
      return origin.apply(this, args_resolution(keys, vals, Array.prototype.slice.call(arguments)));
    };
  }
  return func;
};