Help us understand the problem. What is going on with this article?

JavaScript における pipeline-operator とは

More than 1 year has passed since last update.

はじめに

この記事では,pipeline-operator の良い点や,現実的にどういったコードが置き換わっていくのか,
その際のメリットなどについて紹介したいと思います.

pipeline-operator って?

そもそもpipeline-operatorがなにかについて紹介したいと思います.
自分の知る範囲では,F#, Elixir, Elm などには,pipeline-operatorがもともと入っています.
(厳密には,F# では Pipe-forward operator)

言語のラインナップを見て勘のいい人はお気づきかもしれませんが,いわゆる関数型言語から影響を受けています.

JavaScriptにおいて pipeline-operator は便利な糖衣構文の一つで,引数を取る関数に対して順番に適用したいときに便利です.
実際のコードを見てみるとわかりやすいです.

(策定中の仕様などは, tc39/proposal-pipeline-operator)

function doubleSay (str) {
  return str + ", " + str;
}
function capitalize (str) {
  return str[0].toUpperCase() + str.substring(1);
}
function exclaim (str) {
  return str + '!';
}

こういった関数が定義されているときに,次のように記述できます.

let result = "hello"
  |> doubleSay
  |> capitalize
  |> exclaim;

result // => "Hello, hello!"

これは,次のように書くのと同じ意味です

let result = exclaim(capitalize(doubleSay("hello")));
result //=> "Hello, hello!"

何が嬉しいの?

上の例だと普通に exclaim(capitalize(doubleSay("hello"))) と書けば良くないか?と思うのが普通だと思います.
なのでもう少し現実的に何が嬉しいのか見ていきましょう.

同じinterfaceのものを組み合わせるとき便利

例えば,あるObjectをバリデーションする場面を考えてみます.
その際に,統一した型で関数を実装します.
(例えば,<T>(...args: any) => (obj: T) => T のようなinterface を満たすような型で実装します.)
高階関数になっていることがミソです.

function bounded(prop: string, min: number, max: number) {
  return (obj: any) => {
    if (obj[prop] < min || obj[prop] > max) throw Error('out of bounds');
    return obj;
  };
}
function format(prop: string, regex: RegExp) {
  return (obj: any) => {
    if (!regex.test(obj[prop])) throw Error('invalid format');
    return obj;
  };
}

このような関数とpipeline-operatorと合わせて利用すると次のように使えます.

function createPerson(attrs) {
  attrs
    |> bounded('age', 1, 100)
    |> format('name', /^[a-z]$/i)
    |> Person.insertIntoDatabase;
}

処理の流れが見やすいのと,どういったカラムのバリデーションをしているかがよりわかりやすいと思います.
例えば,ここに email の validation を足してみましょう

 function createPerson(attrs) {
   attrs
     |> bounded('age', 1, 100)
     |> format('name', /^[a-z]$/i)
+    |> format('email', /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)
     |> Person.insertIntoDatabase;
 }

このように,インターフェースが整っている場合に限って足したり減らしたり並び替えたりが簡単にできます.
実は,こういった使い方Reactの開発の場合だとすでに使っている場合があります.
具体的に言うと,HOCを recompose の compose を使って結合している場面です.

すでに現実にあるコードは,pipeline-operator でどう置き換わるか

上でも少し触れたのですが,pipeline-operator に近いことを関数で実現するためのものは実はすでにたくさんあります.
Reactの開発でよくあるのが,recomposeの compose() です.
他にも,lodashの flow() や,ramda.js の pipe() は,本来pipeline-operatorで表現したかったもものだと思います.

recompose/compose の例

React開発で,頻出するのが HOC (Higher-Order Components) です.
高階関数のComponent版です.HOCはinterfaceが統一されており,ReactのComponentを引数にとり,ReactのComponentを返却値にする という
統一されたinterface になってます. さきほど紹介した,高階関数の例に非常によく似ています.

export default compose(
  pure,
  connect(mapStateToProps, mapDispatchToProps),
  onlyUpdateForKeys(['label', 'text']),
)(AnyComponent)

上記のコードは,pipeline-operator に簡単に置き換えれます

export default AnyComponent
  |> pure
  |> connect(mapStateToProps, mapDispatchToProps)
  |> onlyUpdateForKeys(['label', 'text'])

(余談ですが,React v17 でそもそもHOCの出番がなくなるかもしれません.

先取りして使う

babelのversion7以降を利用することでpipelineを利用することが出来ます.
.babelrc に次の設定を追記します.

{
  "plugins": [["@babel/plugin-proposal-pipeline-operator", { "proposal": "smart" }]]
}

このプラグインを足すことで,pipeline-operator が transpile 可能になり先取りして利用することが出来ます.

pipeline-operator の仕様の現状

ここ(tc39/proposals)を見ればわかります.
pipeline-operatorについて,最後にディスカッションされたのは3月です.(チャンピオンという,担当者的な方が忙しいらしい)

リポジトリを見てみると,現状 stage-1 であることがわかります.
各種ステージは次のように定められています.

段階 概要
Stage-0:Strawman アイデア
Stage-1:Proposal Polyfill や babel-plugin を使ってデモ
Stage-2:Draft ECMAScript標準と同じルールでAPIや構文、セマンティックについて説明
Stage-3:Candidate 仕様は完成した状態
実装や外部のフィードバックを求める状態
レビュアはその仕様策定者以外ならだれでもなれるが専門的な知識を持っている必要がある
Stage-4:Finished 2つの実装(not Polyfill)が必要, ECMAScriptへ取り込まれる準備が完了したことを示す状態

https://azu.github.io/slide-what-is-ecmascript/slide/12.html より引用

まとめ

  • pipeline を使うことで一部の処理を関数を使うことなくスマートに記述できる
  • 既存のコードをスマートにできる例もある
    • lodash の flow
    • ramda の pipe, pipeP
    • recompose の compose
    • など
  • babel-plugin を使えば今からでも利用できる
    • プロダクションに入れるのはリスクかも
  • pipeline-operatorの現状はstage-1

pipelineがstage-4になるのが待ち遠しいですね

smart syntaxについては後で追記します

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away