JavaScript関数型プログラミング

昨日、”Functional Programming For Your Everyday JavaScript Developer”という記事を途中まで読みました。関数型プログラミングの記事はこれまで何度も読みかけていつも挫折しています。JavaScriptでは以下の3つのパラダイムでコードを記述できるとの説明まではうなづきつつ読めるし、もちろん関数とはなにかはわかっています。

  1. 手続き型
  2. オブジェクト指向
  3. 関数型

でも、読み進められないのです。しかし、昨日は少しだけ手を動かしたので忘れないようにメモしておきます。

この記事では、以下のような出力をするプログラムコードが示されていました。

1 bottle of beer on the wall, 1 bottle of beer. Take one down, pass it around no more bottles of beer on the wall.
2 bottles of beer on the wall, 2 bottles of beer. Take one down, pass it around 0 bottle of beer on
the wall.
3 bottles of beer on the wall, 3 bottles of beer. Take one down, pass it around 1 bottles of beer on the wall.
:
:
:
98 bottles of beer on the wall, 98 bottles of beer. Take one down, pass it around 96 bottles of beer on the wall.
99 bottles of beer on the wall, 99 bottles of beer. Take one down, pass it around 97 bottles of beer on the wall.
100 bottles of beer on the wall, 100 bottles of beer. Take one down, pass it around 98 bottles of beer on the wall.

これを関数型ブログラミングのアプローチを用いると次のような形で書けるとのことです。

const beerLyrics = pipe(generateArray, generateLineOne, generateLineTwo, log);
beerLyrics(100);

でも上記のgenerateArray等のコードまで示されてはいませんでした。そこで自分で考えてみようと思いました。

出力結果

まず出力結果は短く日本語としました。

ポケットの中にはビスケットが1個、ポケットをたたくとビスケットは2個。 そんな不思議なポケットがほしい。
ポケットの中にはビスケットが2個、ポケットをたたくとビスケットは3個。 そんな不思議なポケットがほしい。
ポケットの中にはビスケットが3個、ポケットをたたくとビスケットは4個。 そんな不思議なポケットがほしい。
ポケットの中にはビスケットが4個、ポケットをたたくとビスケットは5個。 そんな不思議なポケットがほしい。
ポケットの中にはビスケットが5個、ポケットをたたくとビスケットは6個。 もうポケットに入らないよ。

プログラム例1 – 手続き型

最初のプログラムコードは次です。14行です。

const biscuitLyrics1 = (max) => {
  for (let i = 1; i <= max; i++) {
    const lyric = `ポケットの中にはビスケットが${i}個、ポケットをたたくとビスケットは${
      i + 1
    }個。 ${
      i >= max
        ? 'もうポケットに入らないよ。'
        : 'そんな不思議なポケットがほしい。'
    }`;
    console.log(lyric);
  }
};

biscuitLyrics1(5);

プログラム例2 – 関数型

続いて関数型です。自力ではできなかったので、優秀な理系大学生の力を借りました。

const generateArray = (num) => new Array(num).fill('');

const generateLineOne = (arr) =>
  arr.map(
    (lyric, i) =>
      lyric +
      `ポケットの中にはビスケットが${i}個、ポケットをたたくとビスケットは${
        i + 1
      }個。 `
  );

const generateLineTwo = (arr) =>
  arr.map(
    (lyric, i, orginal) =>
      lyric +
      `${
        i >= orginal.length - 1
          ? 'もうポケットに入らないよ。'
          : 'そんな不思議なポケットがほしい。'
      }`
  );

const log = (arr) => arr.forEach((lyric) => console.log(lyric));

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

const biscuitLyrics2 = pipe(
  generateArray,
  generateLineOne,
  generateLineTwo,
  log
);

biscuitLyrics2(5);

35行と長くなりましたが、配列を作る → 1文目を設定 → 2分目を設定 → console.log出力 という処理要素に切り分けて、各処理要素の出力を次の処理要素の入力にするという所謂パイプライン処理となっています。

将来の書き方

プログラミング言語によっては、パイプ処理演算子というのがあるようで、JavaScriptでもまだブラウザやNode.jsで現在(2020年10月)実装されていないものの仕様は決まっているようです。

これが実装されたら上のコードは次のように変更できます。

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

const biscuitLyrics2 = pipe(
  generateArray,
  generateLineOne,
  generateLineTwo,
  log
);

↓↓↓

const biscuitLycs = generateArray |> generateLineOne |> generateLineTwo |> log;

まとめ

はっきりいって、関数型プログラミングは用語も聞き慣れず、まだ全くわかりません。少しづつ勉強していきたいと思います。