本記事は『なっとく!関数型プログラミング』の「第1章 関数型プログラミングを学ぶ」から一部を抜粋したものです。掲載にあたって編集しています。
※本書はMichał Płachtaによる『Grokking Functional Programming』(Manning Publications 2022)の邦訳版です。
あなたが本書を手に取ったのはきっと...
関数型プログラミングに興味があるから
関数型プログラミングの話を聞いたか、ウィキペディアのエントリを読んだか、本を何冊か読んでいる。おそらくそうしたコードが数学的に説明されているのを見て目を丸くしたが、それでもどうも気になっている。
これまでにないほどとっつきやすい関数型プログラミングの本を書きたいと夢見てきた。それが本書である。初心者レベルの、実用的な、苛立ちを極力なくした本である。
関数型プログラミングを学ぼうとしたことがあるから
一度ならず関数型プログラミングを学ぼうとしたことがあるが、まだ理解できていない。主な概念を1 つ理解したかと思うと、次の障害がすぐそこで待ち構えている。そして、この障害に近づこうとするだけで、さらに多くのことを学ばなければならない。
関数型プログラミングを学ぶのは楽しくないと。本書は読者に小さな一歩を踏み出させる。エンドルフィンがあなたを前に進ませてくれるだろう。
まだ様子見をしているから
あなたはオブジェクト指向プログラミング言語か命令型プログラミング言語で長年プログラミングをしている。関数型プログラミングのうわさを耳にし、ブログの記事をいくつか読み、少しコードを書いてみたりした。それでも、自分のプログラミング作業がどう快適になるのかわからない。
本書では、関数型プログラミングの実践的な応用に重点を置いている。そのようにすると、関数型の概念が頭の中の道具箱にいくつか追加されるはずだ。それらの概念はどの言語を使ったとしても活用することができる。
あるいはその他の理由で
どのような理由があるにせよ、本書は異なる方法で対処を試みる。実験と遊びを通して学ぶことに焦点を合わせる。疑問を投げかけ、コーディングによって答えを導き出すように働きかける。プログラマとしての次なるレベルへの成長を後押しする。ぜひ楽しんでほしい。
どのような知識が必要か
本書では、Java、C++、C#、JavaScript、Pythonなどの一般的な言語のいずれかでソフトウェアを開発した経験があることを前提としている。漠然とした前提条件なので、私たちの認識が一致していることを確認するために簡単なチェックリストを用意した。
本書の内容を問題なく理解できる条件
- クラスやオブジェクトなど、オブジェクト指向の基本的な概念をよく知っている。
- 次のようなコードを読んで理解できる。
最も大きな効果が期待できる条件
- ソフトウェアモジュールの安定性、テスト容易性、後戻り、または統合の問題を経験している。
- 次のようなコードのデバッグで問題が起きたことがある。
十分条件
- オブジェクト指向のエキスパートである。
- Java/C++/C#/Pythonの達人である。
- Kotlin、Scala、F#、Rust、Clojure、Haskellなどの関数型プログラミング言語についての何がしかの知識がある。
関数とはどのようなものか
前置きはこれくらいにして、さっそくコードを見ていこう。必要なツールはまだ完全に揃っていないが、とにかく見てみよう。
ここにはさまざまな関数があり、それらすべてに入力として何らかの値を受け取り、何かを実行し、おそらく出力として値を返すという共通点がある。
すべての関数がpublic static なのはなぜか
ここでおそらく、各定義のpublic static 修飾子が気になっていることだろう。もちろん、この修飾子は理由があってそこにある。本書で使う関数はすべて静的である(つまり、オブジェクトのインスタンスを実行する必要はない)。呼び出し元が要求された入力パラメータを渡す限り、誰でもどこからでも呼び出すことができる。これらの関数は呼び出し元から渡されたデータだけを処理し、それ以上のことは何もしない。
もちろん、これには大きな問題がいくつかある。それらの問題については、本書の後半で説明する。さしあたり、本書で関数と言うときは、どこからでも呼び出せるpublic static 関数のことだと覚えておこう。
関数に取り組む
ここまで見てきたように、関数にはさまざまな種類がある。基本的には、関数はそれぞれシグネチャと本体(シグネチャの実装)で構成される。
本書では、値を返す関数に焦点を当てる。というのも、後ほど説明するように、関数型プログラミングの中心にあるのはこれらの関数だからだ。何も返さない(つまり、戻り値がvoid の)関数は、本書では使わない。
関数については、入力値を受け取り、その値を使って何かを行い、出力値を返すボックスとして扱うことができる。ボックスの中身が本体であり、入力値と出力値の型と名前はシグネチャの一部である。したがって、add 関数を次のように表すことができる。
シグネチャと本体
先の図では、関数の実装である本体はボックスの中に隠れているが、シグネチャは公開されている。この違いは非常に重要だ。シグネチャを見ただけでボックスの中で行われることが十分に理解できるとしたら、コードを読むプログラマにとって大きなメリットになる。なぜなら、関数を使う前にその内部を調べて、どのように実装されているのかを分析する必要がないからだ。
コードが嘘をつくとき
プログラマが直面する最も難しい問題のいくつかは、コードが想定外の何かを行ったときに発生する。こうした問題は、シグネチャと本体の内容の食い違いに関連していることが多い。この問題を実際に確認するために、先の4つの関数をもう一度見てみよう。
驚いたことに、この4つの関数のうち3つは嘘をついている。
Q 関数は嘘をつけるのか?
A 残念ながら、嘘をつける。上記の関数のいくつかは真顔で嘘をついている。通常は、シグネチャが本体についてすべてを語っていないことに起因する。
getFirstCharacter()は、Stringが渡されると、charを返す。しかし、こっそり空のString を渡すと、何の文字も返さず、例外をスローする。
divide()は、bとして0が渡された場合、約束していたintを返さない。
eatSoup()は、渡されたスープを飲むと約束するが、スープを渡しても何もせず、voidを返す。これはおそらくほとんどの子供のデフォルトの実装である。
これに対し、add()はaやbとして何が渡されても約束どおりintを返す。こういう関数は信用できる! 本書では、嘘をつかない関数に焦点を合わせる。シグネチャには、本体のことを包み隠さずしゃべらせたい。本書では、この種の関数だけを使って現実のプログラムの作成方法を学ぶ。
命令型と宣言型
一部のプログラマは、プログラミング言語を命令型と宣言型の2つの主なパラダイムに分類する。簡単な練習問題を通して、これら2つのパラダイムの違いを理解することにしよう。
単語ベースのゲームでスコアを計算する関数を作成することになったとしよう。プレイヤーが単語を入力すると、関数がスコアを返す。単語を構成している文字ごとに1ポイントが与えられる。
スコアを命令型で計算する
命令型プログラミングは、結果をどのように計算すべきかに焦点を合わせる。要するに、特定の手順を特定の順序で定義する。段階的なアルゴリズムを詳細に定義することで、最終的な結果を得る。
スコアを宣言型で計算する
宣言型アプローチの焦点は、どのように行うかではなく、何を行う必要があるかである。この場合は、この文字列の長さが必要であり、この単語のスコアとしてその長さを返すことになる。このため、JavaのStringのlengthメソッドを使って文字数を取得するだけでよい。それがどのように計算されるのかは問題ではない。
それから、関数の名前もcalculateScoreからwordScoreに変更している。些細な違いに思えるかもしれないが、名詞を使うと頭が宣言モードに切り替わり、何かを達成する方法ではなく、何をする必要があるかに集中できるようになる。
通常は、宣言型のコードのほうが命令型のコードよりも簡潔でわかりやすい。JVMやCPUといった多くの内部構造はかなり命令的だが、wordScore関数で行ったように、私たちアプリケーション開発者は宣言型のアプローチを駆使することで、命令型の内部メカニズムを隠蔽することができる。本書では、宣言型のアプローチを使って現実的なプログラムを作成する方法を学ぶ。
関数型プログラミングを学ぶとどんなメリットがある?
関数型プログラミングは、次のような関数を使うプログラミングである。
- シグネチャが嘘をつかない
- 本体が極力宣言的である
本書では、これらのトピックを少しずつ掘り下げていく。最終的には、古い習慣に囚われることなく、現実的なプログラムを構築できるようになるだろう。これだけでも状況はがらりと変わる。ただし、メリットはそれだけではない。本書で関数型プログラミングを学ぶことには、他にも副次的なメリットがある。
すべての言語に通用するコーディングスタイル
ここまではJavaを使って関数を書いてきたが、Javaはオブジェクト指向の命令型言語と見なされている。つまり、宣言型と関数型のプログラミングの手法や機能は、Javaをはじめとする従来の命令型言語に活躍の場を広げている。どの言語を選択したとしても、すでにいくつかの手法を利用できる状態だ。
関数型の概念はどの関数型プログラミング言語でも同じ
本書では、関数型プログラミングの一般的かつ普遍的な機能と手法に焦点を合わせている。本書の概念を学ぶためにScalaを使った場合は、他の多くの関数型プログラミング言語でもそれらの概念を応用できる。つまり、たった1つの言語の特性ではなく、多くの関数型プログラミング言語に共通するものに焦点を合わせている。
関数型の思考と宣言型の思考
ここで習得する最も重要なスキルの1 つは、プログラミング問題を解くための別のアプローチである。そうした関数型の手法をすべてマスターすれば、ソフトウェアエンジニアリングの道具箱に非常に強力な道具が新たに追加される。これまでどのような道のりを歩んできたとしても、この新たな視点はプロとしての成長を間違いなく後押しするだろう。