関数型

■関数型プログラミング

関数型プログラミングとは命令(手続き)型、オブジェクト指向型と同じ
プログラミングパラダイムの1つで複数の関数を組み合わせて開発を行う手法です。

	プログラミングパラダイム:
		プログラミングパラダイムとはプログラマのプログラムに対する
		見方、アプローチ方法のことで、どのパラダイムを使用するかで
		設計、処理内容が異なる

●命令(手続き)型プログラミング
	言語:
		C、C++
	特性:
		命令型プログラミングは問題を解決するために必要な命令を
		適切に並べていくプログラミング方法
		並べた命令が順次実行されることにより処理が適切に実行され、
		問題を解決する

●オブジェクト指向型プログラミング
	言語:
		Java、C++

	特性:
		オブジェクト指向型プログラミングは問題の性質を
		オブジェクト単位で分けるプログラミング方法
		分けられたオブジェクト同士がメッセージを送りあうことにより
		処理が実行され、問題を解決する

●関数型プログラミング
	言語:
		OCaml(オーキャムル)、Scala(スカラ)、Haskell(ハスケル)、F#、JavaScript

	特性:
		関数型プログラミングは問題の性質を全て関数に分けるプログラミング方法
		分けられた関数を組み合わせることにより処理が実行され、問題を解決する

●サンプル
	サンプル

■関数

関数型プログラミングの「関数」とは「入力からのみ出力が決定する」を
満たしたもののみを「関数」と考えます。
この特性に合わない関数は「手続き」と考え、関数とは区別されます。
※これ以降は関数と手続きは分けて記述します。

●独立性
	上でも書いていますが、関数は入力(引数)の情報のみにより出力が決まります。
	これは外的な要因が含まれないことを示していますので、
	1つ1つの関数は高い関数独立性(モジュラリティ)が保障されています。

●定義域(ていぎいき)と値域(ちいき)
	関数に対し、入力の範囲を全体の集合を定義域、出力の範囲全体の集合を値域と呼びます。
		
	例:
		// 最大HP
		const int HpMax = 9999;

		/*
			HpRecovery
				戻り値:
					回復したHP
				引数:
					int now:
						現状のHP
					unsigned int amount:
						回復量
				内容:
					回復量を加算したHPを
					上限範囲チェックをした上で返す

				定義域(引数の範囲):
					now => 整数

					amount => 整数
				値域(戻り値の範囲):
					整数 ~ 9999
		*/
		int HpRecovery(int now, unsigned int amount)
		{
			if (now + amount > HpMax)
			{
				return HpMax;
			}

			return (now + amount);
		}

●関数合成
	関数合成とは複数の関数を組み合わせることです。
	合成の条件は「関数同士」、「関数Aの値域が関数Bの定義域の範囲内」などがあります。
	以下は関数合成と関数合成ではないコードの比較例です。

	関数合成:
		// 最大HP
		const int HpMax = 9999;
		// 最大回復量
		const int RecoveryAmountMax = 9999;

		/*
			HpCheck
				戻り値:
					0~9999の範囲内のHp
				引数:
					int hp:
						hp
				内容:
					hpを範囲チェックして返す

				定義域(引数の範囲):
					hp => 整数
				値域(戻り値の範囲):
					0 ~ 9999
		*/
		int HpCheck(int hp)
		{
			if (hp > HpMax)
			{
				return HpMax;
			} else if (hp < 0) {
				return 0;
			}
			return hp;
		}

		/*
			RecoveryAmountCheck
				戻り値:
					0~9999の範囲内の回復量
				引数:
					unsigned int amount:
						回復量
				内容:
					回復量を範囲チェックして返す

				定義域(引数の範囲):
					amount => 整数
				値域(戻り値の範囲):
					0 ~ 9999
		*/
		unsigned int RecoveryAmountCheck(unsinged int amount)
		{
			if (amount > RecoveryAmountMax)
			{
				return RecoveryAmountMax;
			} else if (amount < 0) {
				return 0;
			}
			return amount;
		}

		/*
			HpRecovery
				戻り値:
					回復したHP
				引数:
					int now:
						現状のHP
					unsigned int amount:
						回復量
				内容:
					回復量を加算したHPを
					範囲チェックをした上で返す

				定義域(引数の範囲):
					now => 整数

					amount => 整数
				値域(戻り値の範囲):
					0 ~ 9999
		*/
		int HpRecovery(int now, unsigned int amount)
		{
			return HpCheck(now + RecoveryAmountCheck(amount));
		}

	非関数合成:
		// 最大HP
		const int HpMax = 9999;
		// 最大回復量
		const int RecoveryAmountMax = 9999;
		// 現在のHP
		int g_Hp = 0;

		/*
			HpCheck
				戻り値:
					なし
				引数:
					なし
				内容:
					g_Hpの範囲チェックをする
		*/
		void HpCheck(void)
		{
			if (g_Hp > HpMax)
			{
				g_Hp = HpMax;
			} else if (hp < 0) {
				g_Hp = 0;
			}
		}

		/*
			RecoveryAmountCheck
				戻り値:
					0~9999の範囲内の回復量
				引数:
					unsigned int amount:
						回復量
				内容:
					回復量を範囲チェックして返す

				定義域(引数の範囲):
					amount => 整数
				値域(戻り値の範囲):
					0 ~ 9999
		*/
		unsigned int RecoveryAmountCheck(unsinged int amount)
		{
			if (amount > RecoveryAmountMax)
			{
				return RecoveryAmountMax;
			} else if (amount < 0) {
				return 0;
			}
			return amount;
		}

		/*
			HpRecovery
				戻り値:
					回復したHP
				引数:
					int now:
						現状のHP
					unsigned int amount:
						回復量
				内容:
					回復量を加算したHPを
					範囲チェックをした上で返す

				定義域(引数の範囲):
					now => 自然数全体

					amount => 自然数全体

				値域(戻り値の範囲):
					0 ~ 9999
		*/
		int HpRecovery(unsigned int amount)
		{
			g_Hp += RecoveryAmountCheck(amount);
			HpCheck();
			return g_Hp;
		}

	関数合成では使用している「HpCheck」「RecoveryAmountCheck」「HpRecovery」は
	いずれも「入力(引数)の情報のみを使用して出力(戻り値)」しているので「関数」といえます。
	また、HpRecoveryではHpCheck、RecoveryAmountCheckを使用していますが、
	以下のように定義域、値域の関係になっているので「関数Aの値域が関数Bの定義域の範囲内」です。

		int HpRecovery(int now, unsigned int amount)
		{
			return HpCheck(now + RecoveryAmountCheck(amount));
		}

		HpRecovery内での各関数の引数
			HpRecovery(int now, unsigned int amount)
				now => 自然数全体
				amount => 自然数全体

			RecoveryAmountCheck(amount)
				amount => 自然数全体

				定義域 => 自然数全体

			HpCheck(now + RecoveryAmountCheck(amount))
				自然数 + 0~9999

				定義域 => 自然数全体

	一方非関数合成ではHPがグローバル変数として扱われています。
	そのため、「HpCheck」と「HpRecovery」は関数の特性である
	「入力のみを使用して出力する」にあてはまっていません。
	非関数合成は「RecoveryAmountCheck関数」と「HpCheck手続き」を組み合わせた処理になります。

		理由:
			HpRecovery
				入力、出力がないので関数の定義から外れている

			HpCheck
				引数であるamountの値が同じでもg_Hpの値によって左右されるので
				関数の定義から外れている

■副作用

プログラミングにおいて状態を参照し、状態に変化を加えることで、
次回の処理にまで影響を与える効果のことを副作用と呼びます。

●代入
	副作用の中で最も行われている処理は変数への代入です。
	「●関数合成」の非合成関数のコードでもグローバル変数を使用することで
	関数内に外的要因が入ってしまい、「関数」が「手続き」になっています。
	関数型プログラミングでは副作用が生じる代入を「破壊的代入」
	副作用が生じない代入を「束縛」と呼び区別しています。

	・破壊的代入(再代入)
		既に何らかの値が入っている変数に対して新たな値を上書きする代入方法

		例:
			int x = 0;
			x = 10; 
	・束縛
		1度しか値を代入しない(できない)代入方法

		例:
			const int x = 100;
			x = 1000; // エラー

	関数型言語では副作用が発生する破壊的代入は使用できないものが多く、
	「変数宣言 = 束縛」という認識でコーディングされています。

●入出力
	キーボードの入出力やファイルの読み書きなども副作用と考えられています。
	これらはコンパイラ以前にプリンタやキーボードなどの外的要素に依存するからです。
	キーボードも同じキーを打ったとしても入力モードによって、結果が変わります。
	ファイルも同じファイルを読み込んだとしても外部でファイルが書き換えられていたら
	1度目と2度目で別の内容のファイルになってしまい、
	関数の定義を保つことできません。

		pgtheory_0048

●参照透過性
	参照透過性とは「同じ式は何時評価しても同じ結果になるという性質」のことです。
	これは関数型プログラミングの関数の定義でかなり重要なことです。
	副作用はこの参照透過性の性質を損ねる要因です。
	※この性質を持っている関数型言語を「純粋関数型言語」と呼んでいます。

	参照透過性:
		// 必ずaとbを足した合計になる
		int AddSum(int a, int b)
		{
			return (a + b);
		}

	非参照透過性:
		int g_AccessCount = 0;

		// 呼ばれた回数によって結果が変わる
		int Access(void)
		{
			g_AccessCount++;
			return g_AccessCount;
		}

■関数型と命令型

命令型と関数型では処理を考える際の意識が異なります。

●処理の考え方(命令型):
	・必要な値はどこにあるか
	・覚えておくべき値はあるか
	・呼び出すべき関数はあるか
	・戻り値はあるか
	・処理によって影響がでるデータはあるか

	命令型の関数とは「特定の機能を実行するための命令の列挙」なので
	必要な値の参照先や保存先、途中で呼び出すべき関数など
	様々なことを意識しながら処理をコーディングしていきますが、
	各関数後に処理の性質を変えることができるので
	自由度の高い関数が作成できます。
	※ここの関数の定義だけは命令型としての関数の意味で使用しています。

●処理の考え方(関数型):
	・引数の値から出力を求めるにはどうすればいいか
	・一番適した関数合成の組み合わせはなにか

	関数型では引数の値を利用した結果の値を返すことを主に考えます。
	その結果を出すために関数合成をすべきか、するなら
	どの関数が一番適しているかを考えて関数を作成します。

	・宣言的
		関数型プログラミングの関数は宣言的に作成できます。
		宣言的とは出力の性質(内容)に注目し、その性質だけを処理として
		コーディングすることです。
		宣言的にコーディングすることによりその関数の本質以外のことを
		気にしなくてすみます。
		本質以外の事とは「どの変数を参照すべきか」、「関数を呼び出すことによる
		他の処理への影響はあるか」などです。

■関数型プログラミングを理解するメリット

関数型プログラミングを理解することで命令型のプログラマーが得られる
メリットは以下のようなものがあります。

●テストの簡易化、バグ問題の軽減
	関数型で作成された関数は参照透明性を求める関数なので
	特定の値を入れたら必ず同じ結果が返ります。
	その性質から、単体テストでは最低限のチェックを行えば
	実装は満たしていると考えることができます。
	また、関数が外部の処理に影響を与えることはありませんので、
	バグが発生した場合の影響度も低くなります。

●並列化がしやすい
	現在のGPUやCPUはコア増加により、並列処理が多用されています。
	関数型は並列時のどのようなタイミングで処理が実行されたとしても
	安定した動作をさせやすい技法です。

●コード量の減少
	関数型の関数は宣言的に作成することからシンプルな内容になりやすく、
	必然的に少ないコード量で書かれます。
	関数型を意識することで、命令型のプログラムであっても
	コード量の少なく、保守性の高いコードを書きやすくなります。