1. はじめに
最近JavaScript関数型プログラミング 複雑性を抑える発想と実践法を学ぶを読んで関数型プログラミングについて勉強し始めました。
まだまだ理解しきれていないことが多いので、頭の整理も兼ねて勉強内容のアウトプットしていきます。
今回は関数型プログラミングの基本的な原則を中心にまとめます。
個人的なイメージで記載しているため、認識齟齬あればコメントください。
2. 関数型プログラミングとは
上記の本には、「関数型プログラミング」を簡潔にまとめると、以下のように書いてありました。
関数型プログラミングとは、純粋関数を宣言的に評価することである。
純粋関数は、外部から観測可能な副作用を回避することで、不変性を持つプログラミングを生成する。
この中で重要なものは以下の3つの原則です。
- 宣言型
- 純粋関数
- 不変性
もう少し詳しく見ていきます。
2.1. 宣言型
宣言型とは、ざっくり言うと「目的」の宣言のみを行い、細かいタスクはコンピュータに任せてしまおう!
という考え方のことです。
皆さんに馴染み深い例としてはSQLです。
SQL文は以下のように結果がどのように出力されるかを宣言し、内部処理についてはコンピュータにお任せしています。
SELECT name FROM USERS
宣言型とよく比較されるのが、命令型や手続き型と言われるものです。
命令型とは、どのように実行すべきかを細かく指示するもので、多くのオブジェクト指向言語で使われています。
命令型と宣言型を比較したイメージが以下の図です。
「スーパーへのおつかいを頼む親」というシナリオを想定しています。
命令型と宣言型をプログラムにしたものも見ていきましょう。
配列内の全ての値を2倍するプログラムを考えます。
このプログラムにおける差分は、ループする処理が命令型だとfor文、宣言型だとmap関数である点です。
つまり、命令型では「○回繰り返せ」と指示をしていますが、宣言型では、ループの部分をコンピュータに任せているため、
命令型でイテレータとして定義したi
を管理する必要がなくなります。
■命令型
var array = [0, 1, 2, 3, 4, 5]
for (let i = 0 ; i < array.length ; i++){
array[i] = array[i] * 2
}
array;
// -> [0, 2, 4, 6, 8, 10]
■宣言型
[0, 1, 2, 3, 4, 5].map((number) => number * 2);
// -> [0, 2, 4, 6, 8, 10]
2.2. 純粋関数
純粋関数とは、入力する値が同じであれば、出力される値が同じである関数
のことをいいます。
この特性を参照透過性
といいます。
関数型プログラムはこの純粋関数を用いて、不変性を担保しています。
言い方を少し変えると、純粋関数とは関数のスコープ外にある変数を一切使用しません。
つまり、グローバル変数を使用しません。
この原則に則ると、変更に強い実装になります。
イメージしやすいよう、以下に純粋関数になってるパターンとなってないパターンを書いています。
■純粋関数になっている
var increment = counter => counter + 1 ;
■純粋関数になっていない
var counter = 0;
function increment() {
return counter++;
}
2.3. 不変性
不変性とは、1度生成すると変更できないようにすることを指しています。
不変性を持っていない例として、javascriptにおけるobjectがあります。
objectは不変性を持っていないため、sort関数で配列自身の要素が変わってしまいます。
この特性は、言語自体の欠点であるため、javascriptで関数型プログラミングをする場合は、この欠点を克服する必要があります。
克服する方法は今回の記事では割愛します。
3. まとめ
関数型プログラミングの基本原則をまとめました。
他にも関数型プログラミングをうまく活用していくためには、タスクをシンプルな関数に分解していくことも大きなポイントです。分解する単位としては、関数の振る舞いを一つの目的に絞ることです。
例えば、データベースの中から特定のユーザーを絞り出し、出力するシナリオを想定すると、「DBから検索する関数」「出力するための形式を整える関数」「出力する関数」に分解するとすっきりします。
javascriptでは高階関数をうまく使って、関数を引数に用いた関数を使用できるので、上記のシナリオが容易に再現できます。
このようなポイントは原則をもう少し自身の中で深掘りしながら、関数型プログラミングを今後の実戦に活かしていこうと思います。