はじめに
こちらは色々な記事を読み、私なりにまとめた関数型プログラミング
についての記事になります。
コードは全てJavaScriptで書いてあります。
関数型プログラミングとは
関数型プログラミングに関する色々な記事を見て、自分なりにまとめた定義が以下になります、
関数型プログラミングとは、
宣言型プログラミングの1つであり、手続き型のコードを関数に分離し、関数主体のコーディングを行うプログラミング手法
のこと。
// 手続き型のコード。
const nums = [100, 200, 300];
let total = 0;
for (let i = 0; i < nums.length; i++) {
total += nums[i];
}
console.log(total);
// -------------------------------------------------
// 上記の手続き型のコードを、関数型プログラミングのコードに変換。
const nums = [100,200,300];
const sum = (array) => array.reduce((accumulator, currentValue) => accumulator + currentValue)
console.log(sum(nums));
上記は手続き型のコードを、配列で利用できるreduce関数
を使い関数型プログラミングのコードに変換しています。
for文を使わずreduce関数を使って関数を定義し、sum
という関数名からもわかるようにどんな処理を行うコードなのか明確になりました。
関数型プログラミングにおける基本概念
以下、関数型プログラミングの基本概念になります。
- 純粋関数(Pure Function)
- 状態と処理を分離
- 不変性(Immutability)
- データ操作の抽象化(Data Abstraction)
- 高階関数(Higher-Order Functions)
- 再帰(Recursion)
今回は、純粋関数
、状態と処理を分離
をピックアップして解説します。
純粋関数とは
純粋関数とは以下の条件を満たした関数のことです。以下の条件から少しでも外れた関数は純粋関数ではありません。
1. 参照透過性
2. 副作用がない
3. 引数で渡された値を変更しない(Immutabilityの保持)
1つずつ解説していきます。
1. 参照透過性とは
Wikipediaでは以下のように定義されています。
計算機言語の概念の一種である。ある式が参照透過であるとは、その式をその式の値に置き換えてもプログラムの振る舞いが変わらない(言い換えれば、同じ入力に対して同じ作用と同じ出力とを持つプログラムになる)ことを言う。
要約すると、関数等の数式に同じ数値を入れた時、同じ処理と同じ計算結果をもたらす式のこと
です。
function add(val1, val2) {
return val1 + val2;
}
add(1, 2); // ->3
上記のadd(1、2)
はどれだけ繰り返しても3
を返却します。これを参照透過性
と言います。
2. 副作用がないとは
プログラミングにおいて、式の評価による作用には、主たる作用とそれ以外の副作用(side effect)とがある[1][2]。 式は、評価値を得ること(※関数では「引数を受け取り値を返す」と表現する)が主たる作用とされ、それ以外のコンピュータの論理的状態(ローカル環境以外の状態変数の値)を変化させる作用を副作用という
要約すると、関数が引数を受け取り値を返す処理、それ以外の処理のこと
になります。
以下に、副作用の具体例をいくつか列挙します。
- コンソールへのログ出力
- 関数外の状態(データ)の参照・変更
- DOM操作
- サーバーとの通信
- タイマー処理
- ランダムな値の生成
const a = 1;
function add(val1, val2) {
console.log(a); // コンソールへのログ出力、関数外の状態(データ)の参照・変更
fetch('https://www.google.com/'); // サーバーとの通信
return val1 + val2;
}
add(1, 2); // ->3
上記のコードだと、console.log(a);
とfetch('https://www.google.com/');
の2つの処理は、関数が引数を受け取り値を返す処理
以外の処理なので副作用
といえます。
3. 引数で渡された値を変更しない(Immutabilityの保持)
// 引数で受け取った配列に新しい要素を追加する関数
function addElement(array, element) {
array.push(element); // 配列を変更しているため、純粋ではない
return array;
}
const originalArray = [1, 2, 3];
const modifiedArray = addElement(originalArray, 4);
console.log(originalArray); // [1, 2, 3, 4]
console.log(modifiedArray); // [1, 2, 3, 4]
上記のコードでは、addElement関数が引数で受け取ったarrayに対してpushメソッドを使用して要素を追加しています。このような関数は、元の引数の値を変更しているため純粋関数ではありません。
状態と処理を分離
値の状態と処理が分離されていること。データに何らかの処理を加え、連続で組み立てていくことです。
A(data) => b(data) => c(data) => 結果
のように、状態(data)
と 処理(関数A、関数B、関数C)
が分離され、それぞれの関数の結果を次の関数の引数に渡し、最終的に結果を出力することを言います。
const processData = (data) => {
const stepA = A(data);
const stepB = B(stepA);
const stepC = C(stepB);
return stepC;
};
const A = (data) => {
// 処理を行う
return modifiedData;
};
const B = (data) => {
// 処理を行う
return modifiedData;
};
const C = (data) => {
// 処理を行う
return modifiedData;
};
const inputData = ...; // 入力データ
processData(inputData);
上記のコードでは、関数 processData が A、B、C の順に処理を行い、最終的な結果を返します。各関数は引数としてデータを受け取り、処理を行った結果を返します。
関数型プログラミングのメリット
関数型プログラミングの考え方でコードを記述していくことで、以下の恩恵を受けることができます。
- コードの可読性の向上
- テスト性の向上
- 拡張性・再利用性の向上
- 副作用が発生しずらい
Reactは関数型プログラミングの思想を取り入れている
Reactは関数型プログラミングの思想を取り入れて作られています。
- 関数の引数に渡ってくるpropsを変更する事ができない。
- useStateのコールバック関数の引数に渡ってくる値がオブジェクト型の場合は、コピーして戻さないと状態が更新されない(Reduxも同じ)。
- Reactのコンポーネントは純粋関数の考え方に則っているため、関数コンポーネントのトップレベルに副作用を書くことは非推奨となっている。副作用を書く場合はuseEffectの中に原則書く。
オブジェクト指向プログラミングとは
関数型プログラミングとよく比較されるオブジェクト指向プログラミングとは
オブジェクト指向プログラミングとは、命令型プログラミングの1つであり、相互に作用するオブジェクトを組み合わせてプログラムを設計する手法のこと。
関数型プログラミングとオブジェクト指向プログラミングとの違い
- 関数型プログラミングは状態と処理を分離させるが、オブジェクト指向プログラミングは状態と処理をクラスで保持する。
- 関数型プログラミングは宣言型プログラミングの1つだが、オブジェクト指向プログラミングは命令型プログラミングの1つ。
まとめ
- 関数型プログラミングは宣言型プログラミングの1つで、状態と処理を分離させてコードを組み合わせていく。
- 関数型プログラミングは
純粋関数
や状態と処理を分離
、不変性
などの重要な概念が存在する。 - 関数型プログラミングの考え方でコードを記述していくことで、
コードの可読性や
テスト容易性
など様々な恩恵を受けられる。
終わりに
ここまで記事を読んでいただきありがとうございました。
関数型プログラミングはまだまだ理解が浅く、更に経験を積み重ねながらインプットした内容は記事に落とし込んでいきたいと思います。
間違いなどありましたらご指摘いただけると幸いです。
参考