Chapter 11

なぜmapやreduceやfilterなのか〜後編

とっくり
とっくり
2022.06.14に更新

前回の続きです。

https://zenn.dev/tockri/books/dcaf6c55e64448/viewer/4d2edc

前回のおさらい:FizzBuzzの大部分がテストできた😃

JavaでFizzBuzzを書いて、Stream APIを利用するとロジックのほとんど全部を純粋関数にできて、テスト可能になりました。でも、なんかモヤモヤしませんか、という内容でした。

・言うてもforループなんかで間違えなくない?
・IntStream.rangeClosed 呼ぶとかってforループより面倒くさくない?
・そのmapToObjとか用意されてるAPI全部覚えなきゃ使いこなせなくない?

どこでバグを入れてしまうのか

・言うてもforループなんかで間違えなくない?

プログラマはどういう時に、どういう箇所で、バグを入れてしまうのでしょうか。

答えは「いつでもどこでも」です。

自分なんて信じちゃいけません。「テストの無いコードはバグってると思え」です。

逆に、テストされてバグがないことが保証されている関数が増えれば増えるほど、それらの組み合わせで、安心して清々しい気持ちで高品質なプログラムを素早く作れます。

ですから、
テストされた、バグのない純粋関数がたくさんある状態
を目指して日々のコードを書いていきましょう。

ちなみに、前回作った

Stream<String> fizzBuzzStream(int from, int to)

も、入力が引数だけ、出力が返り値だけ、副作用なし、なので、もちろん純粋関数ですよね。テストが捗る純粋関数です。純粋関数サイコー。

知らなくても探せる

・IntStream.rangeClosed 呼ぶとかってforループより面倒くさくない?
・そのmapToObjとか用意されてるAPI全部覚えなきゃ使いこなせなくない?

さて、前回サンプルコードを書きましたが、実は、僕はそれまでjavaのIntStreamクラスや mapToObjメソッドを知りませんでした。(List.stream()などでmapfilterは使ってましたが。)

それでもAPIドキュメントを見ながらサクサク実装できたのは、

  • Integerをfromからtoまでイテレートするような Stream<Integer> を作れるクラスがあるはず
  • そのクラスに、シグネチャが<T> Stream<T> map(Function<Integer, T> func)であるようなmapっぽいメソッドがあるはず

という予測があったからです。

この予測こそ、関数型プログラミングの文脈からくる知識であり、関数型プログラミングに慣れることのメリットだと思うのです。

実際にはJavaにはプリミティブ型の制約があってScalaに比べると面倒なインターフェイスの IntStreammapToObj があったわけですが、探す箇所はjava.util.streamパッケージ内なのですぐ見つかりました。

mapのイメージ

僕がもっているmap関数のイメージです。(Scalaで形成されたイメージなので、ScalaのListRangeクラスを使ってます)

Intを保持しているロボット(なにかの値を持ってて独自の処理をするオブジェクト)にmap(fizzBuzz)を適用すると、ロボット自身の型は変わらないまま、中身だけがfizzBuzz関数で変換されたStringになります。

この「ロボット」にあたるクラスはListMapといったコレクションに限りません。

  • JavaScriptやTypeScriptのPromiss、ScalaでいえばFutureのような非同期処理が終わったときの値を格納するクラス
  • JavaのOptionalやScalaのOptionのような、非nullの値が入っている場合と入っていない場合を透過的に扱うクラス

こういうクラスに関しても、上記のListなどと同じイメージでmapを定義することができます。(Scalaでは標準ライブラリで実装されています)

Image icons by oNline Web Fonts

一度学習して得られるリターンが大きい

このイメージは、僕はScalaで覚えましたが、大本はHaskellから来たもので、TypeScriptでもSwiftでもrubyでもF#でも全く同じです。(C#のLINQだけは別で、mapではなくSelectという名前)

関数型プログラミングの歴史は古く、現在いろんな言語に少しずつ取り入れられつつある高階関数やモナドといった大きなコンセプトがHaskell言語という形になったのは1990年、インターネットが一般に普及するより前のことだそうです。

mapfilterfoldreduce)、forEachといった名前の関数(高階関数)は上に挙げたどの言語でも、たいていHaskellと同じ意味で、似たシグネチャで実装されています。

ということは、1つの言語で使い方を覚えれば、他の言語でも類推で応用が効くのです。サーバーサイドでScala書いてたらフロントエンドのTypeScriptも読み書きできるようになるみたいな。お得ですね。

だから関数型プログラミングを広めたい

関数型プログラミングの文脈がたくさんの人に共有されていれば、mapみたいなメソッドを作ったり使ったりするとき、いちいち説明する必要がありません。

テスト可能な部分が増えて、高階関数の名前についての共通認識ができる。関数型プログラミングを学習するといいことだらけですね!