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

Pipe関数の力

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の話でした。

Why do not you register as a user and use Qiita more conveniently?
  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