コンピュータプログラミングにおいて、純粋関数とは以下の性質を持つ関数である: [1] [2]
- 関数の戻り値は、同一の引数に対しては同一である(ローカル静的変数、非ローカル変数、可変参照引数、または入力ストリームによる変化はない、つまり参照透過性)。
- 関数には副作用がありません(ローカルの静的変数、非ローカル変数、変更可能な参照引数、または入出力ストリームが変更されません)。
例
純粋関数
次のC++関数の例は純粋です。
不純な関数
次の C++ 関数は、上記のプロパティ 1 がないため、不純です。
- 静的変数の戻り値の変動のため
int f () {静的int x = 0 ; ++ x ;戻り値x ; }
- 非ローカル変数による戻り値の変動のため
同じ理由で、たとえば C++ ライブラリ関数は、その結果が実行時に変更できるIEEE 丸めモード
int f () {戻り値x ; }
sin()に依存するため、純粋ではありません。 - 可変参照引数による戻り値の変動のため
int f ( int * x ) {戻り値* x ; }
- 入力ストリームによって戻り値が変化するため
int f () { int x = 0 ; std :: cin >> x ;戻り値x ; }
次の C++ 関数は、上記のプロパティ 2 がないため、不純です。
- ローカル静的変数の変更のため
void f () {静的int x = 0 ; ++ x ; }
- 非局所変数の変異のため
void f () { ++ x ; }
- 可変参照引数の変異のため
void f ( int * x ) { ++* x ; }
- 出力ストリームの変化による
void f () { std :: cout << "Hello, world!" << std :: endl ; }
次の C++ 関数は、上記のプロパティ 1 と 2 の両方が欠けているため、不純です。
- ローカル静的変数の戻り値の変動とローカル静的変数の変異のため
int f () {静的int x = 0 ; ++ x ;戻り値x ; }
- 入力ストリームによる戻り値の変動と入力ストリームの変異のため
int f () { int x = 0 ; std :: cin >> x ;戻り値x ; }
純粋関数のI/O
I/O は本質的に不純です。入力操作は参照の透明性を損ない、出力操作は副作用を引き起こします。ただし、関連する I/O デバイスの操作シーケンスが引数と結果の両方として明示的にモデル化され、入力シーケンスがプログラムの実行開始以降に実際に行われた操作を記述していない場合は I/O 操作が失敗するとみなされる場合、関数が入力または出力を実行しても純粋である可能性があるという考え方があります。[説明が必要]
2番目のポイントは、引数として使用できる唯一のシーケンスが各I/Oアクションごとに変更されることを保証します。1番目のポイントは、シーケンス引数が変更されたために、I/O実行関数への異なる呼び出しが異なる結果を返すことを可能にします。[3] [4]
I /O モナドは、純粋関数型言語で I/O を実行するために通常使用されるプログラミング表現です。
メモ化
純粋関数の出力は事前に計算してルックアップ テーブルにキャッシュできます。メモ化と呼ばれる手法では、特定の関数から返される結果はすべてキャッシュされ、次に同じ入力パラメータで関数が呼び出されたときには、関数を再度計算する代わりにキャッシュされた結果が返されます。
メモ化は関数を別の関数(ラッパー関数)でラップすることによって行うことができる。[5]
メモ化によって、キャッシュを管理するためのオーバーヘッドとメモリ要件の増加を犠牲にして、関数自体の計算にかかる計算量を削減できます。
階乗のキャッシュ計算を行うCプログラムは、引数が偽の場合にエラー メッセージを表示して中止します。32 ビット マシンでは、それを超える値はいずれにしても表現できません。[引用が必要]assert()fact(12)
static int fact ( int n ) { return n <= 1 ? 1 : fact ( n -1 ) * n ; } int fact_wrapper ( int n ) { static int cache [ 13 ]; assert ( 0 <= n && n < 13 ); if ( cache [ n ] == 0 ) cache [ n ] = fact ( n ); return cache [ n ]; }
コンパイラの最適化
上記の特性2だけを持つ関数、つまり副作用のない関数は、算術演算子に似た共通部分式の除去やループ最適化などのコンパイラ最適化手法を可能にします。 [6] C++の例としてはlength、文字列のサイズを返すメソッドがありますが、これは文字列が指すメモリの内容に依存しているため、上記の特性1がありません。しかし、シングルスレッド環境では、次のC++コード
std :: string s = "Hello, world!" ; int a [ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; int l = 0 ;
( int i = 0 ; i < 10 ; ++ i ) { l += s . length ( ) + a [ i ] ; }
s.length()ループの前に
の値が 1 回だけ計算されるように最適化できます。
一部のプログラミング言語では、関数に純粋なプロパティを宣言できます。
- FortranとDでは、
pureキーワードを使用して、関数が副作用がない(つまり、上記のプロパティ2のみを持つ)ことを宣言できます。[7]コンパイラは、宣言に基づいてプロパティ1を推測できる場合があります。[8]参照: Fortran 95言語機能§純粋手続き。 - GCCでは、
pure属性は特性2を指定しますが、const属性は両方の特性を持つ純粋な関数を指定します。[9] - コンパイル時の関数実行を提供する言語では、関数が純粋であること、場合によっては他の制約が追加されることを要求する場合があります。例としては、
constexprC++(両方の特性)が挙げられます。[10]参照: C++11 § constexpr – 一般化された定数式。
ユニットテスト
純粋関数は同一の引数に対して同一の戻り値を持つため、単体テストに適しています。
参照
- コンパイル時の関数実行 – コンパイル時の純粋関数の評価
- 決定論的アルゴリズム – 特定の入力が与えられた場合、常に同じ出力を生成するアルゴリズム
- 冪等性 – 演算を複数回適用しても結果が変わらないという性質
- ラムダ計算 – 関数に基づく数学的論理システム
- 純粋関数型データ構造 - 純粋関数型言語で実装可能なデータ構造
- 再入可能性(コンピューティング) – 他の呼び出しに干渉することなく関数を同時に実行すること
参考文献
- ^ Bartosz Milewski (2013). 「Haskellの基礎」. School of Haskell . FP Complete. 2016年10月27日時点のオリジナルよりアーカイブ。 2018年7月13日閲覧。
- ^ Brian Lonsdorf (2015). 「Professor Frisby のほぼ十分な関数型プログラミングガイド」. GitHub . 2020 年 3 月 20 日閲覧。
- ^ ペイトン・ジョーンズ、サイモン・L. (2003)。Haskell 98 言語と図書館: 改訂版レポート(PDF)。ケンブリッジ、イギリス: ケンブリッジ大学出版局。p. 95。ISBN 0-521 826144. 2014年7月17日閲覧。
- ^ ハヌス、マイケル。 「Curry: 統合された関数型論理言語」(PDF)。www-ps.informatik.uni-kiel.de。クリスチャン・アルブレヒト大学キール情報研究所。 p. 33. 2014 年 7 月 25 日のオリジナル(PDF)からアーカイブ。2014 年7 月 17 日に取得。
- ^ Aley, R. (2017). プロ機能 PHP プログラミング: パフォーマンス最適化、同時実行性、テスト容易性、コード簡潔性のためのアプリケーション開発戦略。SpringerLink : Bücher. Apress. p. 109. ISBN 978-1-4842-2958-3. 2024年2月4日閲覧。
- ^ 「共通関数属性 - GNU コンパイラ コレクション (GCC) の使用」。gcc.gnu.org、GNU コンパイラ コレクション。Free Software Foundation, Inc. 2018 年 6 月 28 日閲覧。
- ^ Fortran の純粋属性
- ^ D言語の純粋属性
- ^ 「共通関数属性」。GNUコンパイラ コレクション (GCC) の使用。2021 年7 月 22 日閲覧。
- ^ C++ の constexpr 属性
