Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Pipe関数の力

More than 1 year has passed since last update.

Javascriptで役に立つ関数型プログラミングというシリーズを始めます。

今回は、関数型プログラミングの中心的な概念、関数合成について書こうと思います。
関数合成とは〜という学術的な話をするとキリがないので、例を見ていきましょう:

const addFive = num => num + 5;
const double = num => num * 2;
const addFiveAndDouble = num => double(addFive(num));

addFiveAndDoubleaddFivedoubleを合成した関数になります。タスクによっては、二つ以上の関数を合成することが多いと思います。しかしそうなると、コードが以下のように読みづらくなります:

const result = countLikes(pickLongest(excludeCommentsByAuthor(getTodayComments(post))));

関数のネーミング関係なく、カッコが多くて長い表現になってしまいます。

ここに、pipe関数が登場します!

Pipeで関数合成

Javascriptでは、pipeを以下のように定義できます。

// わかりやすくするためにあえてfunctionを使います
function pipe(...fns) {
  return function(arg) {
    return fns.reduce((val, fn) => fn(val), arg);
  }
}

上記の長すぎる表現はpipeを使えばもっと簡潔に書けます:

const result = pipe(
  getTodayComments,
  excludeCommentsByAuthor,
  pickLongest,
  countLikes
)(post);

行う処理も、上が最初で下が最後、人間にとって自然な流れになっています。もちろん左から右に書いても構わないです。Array.prototype.reduceの仕組みがわかる前提で書いてますが、自分でreduceを定義してみると、pipeもわかりやすくなると思います:

function reduce(list, fn, startVal) {
  let acc, startIdx;
  if (arguments.length == 3) {
    acc = startVal;
    startIdx = 0;
  } else {
    acc = list[0];
    startIdx = 1;
  } 
  for (let i = startIdx; i < list.length; i++) {
    acc = fn(acc, list[i]);
  }
  return acc;
}

もちろん、pipeをワンラインで定義したければ、できます:

const pipe = (...fns) => arg => fns.reduce((x, f) => f(x), arg);

pipeの定義を見ればわかりますが、関数の配列fnsはクロージャによって、アクセス可能なので、pipeに関数の配列だけ与えると、その配列を覚えた合成関数が返ってきます。最初に挙げた例に戻ると、たとえば投稿のコメントを処理する合成関数を以下のように作り、再利用できます:

const processPostComments = pipe(
  getTodayComments,
  excludeCommentsByAuthor,
  pickLongest,
  countLikes
);
// ...
// コードのどこかで
fetch(postUrl)
.then(response => response.json())
.then(processPostComments);

おまけに

ちなみに、関数型言語と言われる言語には、合成のために演算子があって、非常に便利です。
Haskellではこういう書き方ができます:

processPostComments = countLikes . pickLongest . excludeCommentsByAuthor . getTodayComments

Javascriptには合成演算子はないですが、将来導入される可能性があります。もしそうなれば以下のような書き方ができるようになります:

let result = post
  |> getTodayComments
  |> excludeCommentsByAuthor
  |> pickLongest
  |> countLikes;

以上、pipeの話でした。

jlkiri
Webでのユーザーインターフェースを作っています @ Yumemi Co., Ltd.
https://www.kirillvasiltsov.com/
yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。Twitterで情報配信中https://twitter.com/yumemiinc
http://www.yumemi.co.jp
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