LoginSignup
3
1

More than 5 years have passed since last update.

メソッドチェーンのパターンを考えた

Last updated at Posted at 2018-07-11

メソッドチェーンのパターンを考えた

おそらく、私はただpipeline operatorが欲しいだけだろう。以下のようなコードを本当に嫌っている。

const x1 = f(x0);
const x2 = g(x1);
const x3 = h(x2);

Promiseの本懐はこれを解決することではないが、メソッドチェーンでpipeline operatorのようなことができるので、好きだ。もともとRubyのObject#tapも好きだったし。

await Promise.resolve(x0).then((x) => {
  return Promise.resolve(f(x));
}).then((x) => {
  return Promise.resolve(g(x));
}).then((x) => {
  return Promise.resolve(h(x));
});

シンタックスはいかついが、変数がクロージャに閉じていて気持ちがいい。こういうものを欲しいと思った時にサクッと対応したいけど、毎回考えてしまうので、今回は一般的に考えた。

RubyのObject#tapの再実装

Object.prototype.tapにブチ込めばよい。

Object.prototype.tap = function (callback) {
  return callback(this);
};

簡単すぎるし、モンキーパッチなので、これを避けていきたい。

基礎パターン (Promise風)

function createThenable(object, callback) {
  const _object = callback(object);
  return {
    value: _object,

    then: function (_callback) {
      return createThenable(_object, _callback);
    },

    await: function () {
      return _object;
    },
  };
}

awaitは、操作感が伝わりやすいかと思い、この命名にした。valueをプロパティで呼ぶだけでもよい。上のコードは、このように使う。

createThenable(1, (i) => {
  return i + 1;
}).then((i) => {
  return i + 1;
}).then((i) => {
  return i + 1;
}).await();

Object#tapが好きだと書いたが、これを再実装するには、createThenableをcreateTappableに、thenをtapに書き換えればそれで済むだろう。よく考えれば、前述したPromiseの副産物はObject#tapと同じものだった。

Array.prototype.pushでメソッドチェーンしたい

そういえば、RubyのArray#pushは返り値がselfなのでメソッドチェーンできるけど、JavaScriptのArrayはそうなっていない。RubyからJavaScriptへ乗り換えた者には覚えがあるだろう。

createThenable([], (array) => {
  array.push(1);
  return array;
}).then((array) => {
  array.push(2);
  return array;
}).then((array) => {
  array.push(3);
  return array;
}).await();

さきほどのコードをそのまま利用するとこうなるが、明らかに冗長なので特化させたい。

function pushArray(array, callback) {
  array.push(callback(array));
  return {
    push: function(_callback) {
      return pushArray(array, _callback);
    },

    array: array,
  };
}

すると、以下のように書くことができる。

pushArray([], () => {
  return 1;
}).push(() => {
  return 2;
}).push(() => {
  return 3;
}).array;

手軽なルーターを書きたい

ルーターというのは、Web Application Frameworkのルーターのことで、正規表現にマッチしたら引数のクロージャを実行する、というのを繰り返すものが欲しい。RegExp.prototype.execの返り値がtruthyの場合に、クロージャに引数として渡したいし、それをマッチするまで繰り返したい。この記事を書くきっかけとなった問題意識がこれ。

function router(routes, regexp, handler) {
  routes.push({ regexp: regexp, handler: handler });
  return {
    bind: function (_regexp, _handler) {
      return router(routes, _regexp, _handler);
    },

    route: function (string) {
      for (let i = 0; i < routes.length; ++i) {
        const r = routes[i];
        const match = r.regexp.exec(string);

        if (match) {
          return r.handler(match);
        }
      }
    },
  };
}

この様に使う。

router([], /aaa/, (match) => {
  return console.log(1);
}).bind(/bbb/, (match) => {
  return console.log(2);
}).route('bbbbbb');

先頭の空配列リテラルが美しくないが、再帰のために必要なものなので、どうしても隠したければこれを隠蔽する関数を書くとよいだろう。ところで、私がルーターと呼んだ「正規表現にマッチしたらクロージャを実行し、マッチしなければ次の候補を試す」ものは頻出だと思うが、通常はどのように書くのだろうか。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1