はじめに
TC39にて議論されているpipeline operatorがかなり興味深く、利便性・可読性が高そうなので自前で実装してみる。
pipeline operatorとは
以下のような関数が入れ子になった処理を書き換えることができる演算子です。
( pipeline operatorは議論中で将来変更される可能性があります。 )
// before
const result = someFunction3(someFunction2(someFunction1("value")));
// after
const result = "value"
|> someFunction1(%)
|> someFunction2(%)
|> someFunction3(%)
// before
const url = `https://${domain}/${dir}?${
Object.keys(query)
.filter(key => allowedQueryKeys.includes(key))
.map(key => `${key}=${query[key]}`)
.join("&")
}`;
// after
const url = query
|> Object.keys(%)
|> %.filter(key => allowedQueryKeys.includes(key))
|> %.map(key => `${key}=${query[key]}`)
|> %.join("&")
|> `https://${domain}/${dir}?${%}`
パイプ演算子(|>
)とトピックリファレンス(%
)が追加されることで、どんな処理を行うのかを上から順に並べることができ、可読性が高くなります。
現行のJavaScriptで同等の記述ができるように工夫してみようと思います
実装
Object バージョン
以下のようなものを実装しました。
function createPipeline(value: T) {
return {
chain: (fn: (value: T) => U) => createPipeline(fn(value)),
end: value,
}
}
これにより以下のように記述することができます
const result = createPipeline("value")
.chain(someFunction1)
.chain(someFunction2)
.chain(someFunction3)
.end;
const url = createPipeline(query)
.chain(Object.keys)
.chain(keys => keys.filter(key => allowedQueryKeys.includes(key)))
.chain(keys => keys.map(key => `${key}=${query[key]}`))
.chain(params => params.join("&"))
.chain(str => `https://${domain}/${dir}?${str}`)
.end;
Class バージョン
クラスの場合以下のように実装できます。
class Pipeline {
value: T;
constructor(value: T) {
this.value = value;
}
chain(fn: (value: T) => U) {
return new Pipeline(fn(this.value));
}
end() {
return this.value;
}
}
利用方法は以下です。
const result = new Pipeline("value")
.chain(someFunction1)
.chain(someFunction2)
.chain(someFunction3)
.end()
const url = new Pipeline(query)
.chain(Object.keys)
.chain(keys => keys.filter(key => allowedQueryKeys.includes(key)))
.chain(keys => keys.map(key => `${key}=${query[key]}`))
.chain(params => params.join("&"))
.chain(str => `https://${domain}/${dir}?${str}`)
.end();