[Fortranのはじめ方] Part21: 再帰関数(RECURSIVE)とPURE/FUNCTION

自分自身を呼び出す関数と、副作用のない安全な関数について学びましょう!

はじめに

モダンFortranでは、プログラムをより効率的かつ安全にするための機能が導入されています。今回はその中でも特に重要な「再帰関数」と「PURE関数」について解説します。これらを理解することで、より複雑なアルゴリズムを簡潔に記述したり、コンパイラによる最適化や並列計算を促進したりすることが可能になります。

再帰関数 (RECURSIVE)

再帰関数とは、自分自身の処理の中で、自分自身を呼び出す関数のことです。数学的な定義や、特定のデータ構造(木構造など)を扱う際に非常に便利です。

Fortranで再帰関数を定義するには、関数定義の先頭に RECURSIVE キーワードを付ける必要があります。また、再帰関数では関数名への直接代入ができないため、RESULT 句を使って戻り値を返す変数を指定するのが一般的です。

基本的な構文

RECURSIVE FUNCTION function_name(arguments) RESULT(result_variable) ! 型宣言など IMPLICIT NONE type, INTENT(IN) :: arguments ! 引数の型と属性 type :: result_variable ! 戻り値の型 ! 終了条件 (これがないと無限ループ!) IF (終了条件) THEN result_variable = ベースケースの値 ELSE ! 自分自身を呼び出す処理 result_variable = ... function_name(...) ... END IF
END FUNCTION function_name 

例: 階乗の計算

nの階乗 (n!) は、n * (n-1) * … * 1 で計算されます。これは再帰的に定義できます。

  • 0! = 1 (終了条件/ベースケース)
  • n! = n * (n-1)! (n > 0) (再帰ステップ)
MODULE factorial_mod IMPLICIT NONE
CONTAINS RECURSIVE FUNCTION factorial(n) RESULT(res) INTEGER, INTENT(IN) :: n INTEGER :: res IF (n < 0) THEN PRINT *, 'Error: Factorial is not defined for negative numbers.' STOP ELSE IF (n == 0) THEN res = 1 ! 終了条件 ELSE res = n * factorial(n - 1) ! 再帰呼び出し END IF END FUNCTION factorial
END MODULE factorial_mod
PROGRAM test_factorial USE factorial_mod IMPLICIT NONE INTEGER :: num = 5 PRINT *, 'Factorial of', num, 'is', factorial(num) ! 出力: Factorial of 5 is 120
END PROGRAM test_factorial 

注意点

再帰関数を実装する際は、必ず終了条件 (ベースケース) を設ける必要があります。終了条件がない、または到達不可能な場合、関数呼び出しが無限に繰り返され、スタックオーバーフローなどのエラーを引き起こします。

PURE関数 (PURE)

PURE関数(純粋関数)とは、副作用 (side effect) を持たない関数のことです。副作用がないとは、関数の実行がプログラムの他の部分の状態(グローバル変数など)を変更せず、関数の結果がその引数のみに依存することを意味します。同じ引数で呼び出せば、常に同じ結果が返されます。

関数をPUREとして宣言するには、関数定義の先頭に PURE キーワードを付けます。PURE関数は、コンパイラによる最適化(例えば、不要な呼び出しの削除や、ループ外への移動など)や、DO CONCURRENT 構文や FORALL 構文などでの並列実行を可能にする上で重要です。

PURE関数の主な制約

PURE関数内では、以下のような副作用を持つ可能性のある操作が禁止されています。
  • 外部ファイルへの入出力 (READ, WRITEなど)
  • STOP 文や PAUSE
  • ALLOCATE, DEALLOCATE, NULLIFY によるメモリ状態の変更
  • SAVE 属性を持つ局所変数
  • COMMONブロックなど、関数の外部にある変数の変更
  • INTENT(IN) 以外の仮引数の変更(サブルーチンの場合は INTENT(OUT), INTENT(INOUT) は可)
  • PUREでない他の手続き(関数やサブルーチン)の呼び出し
  • ポインタの状態を変更する操作

PURE関数の利点

  • 参照透過性: 同じ入力に対して常に同じ出力が得られるため、プログラムの動作が予測しやすくなり、デバッグが容易になります。
  • 最適化: コンパイラは副作用がないことを前提に、より積極的な最適化を行うことができます。
  • 並列化: 各呼び出しが独立しているため、DO CONCURRENT などで安全に並列実行できます。

例: 簡単なPURE関数

MODULE math_mod IMPLICIT NONE
CONTAINS PURE FUNCTION add_squares(a, b) RESULT(res) REAL, INTENT(IN) :: a, b REAL :: res res = a**2 + b**2 ! この関数は引数を変更せず、外部の状態にも影響しない END FUNCTION add_squares ! PUREサブルーチンも可能 (INTENT(OUT) はOK) PURE SUBROUTINE calculate_sum_diff(x, y, s, d) REAL, INTENT(IN) :: x, y REAL, INTENT(OUT) :: s, d s = x + y d = x - y END SUBROUTINE calculate_sum_diff
END MODULE math_mod
PROGRAM test_pure USE math_mod IMPLICIT NONE REAL :: x = 3.0, y = 4.0 REAL :: result_func REAL :: sum_val, diff_val result_func = add_squares(x, y) PRINT *, 'Square sum:', result_func ! 出力: Square sum: 25.0 CALL calculate_sum_diff(x, y, sum_val, diff_val) PRINT *, 'Sum:', sum_val, 'Difference:', diff_val ! 出力: Sum: 7.0 Difference: -1.0
END PROGRAM test_pure 

Fortran 90の組込み関数の多く(特に数学関数)や、Fortran 95の ELEMENTAL 関数は暗黙的にPUREです。

ELEMENTALとの関係

ELEMENTAL キーワードが付いた手続きは、通常PUREでもあります(Fortran 2008以降、IMPURE ELEMENTAL も可能)。ELEMENTAL は、スカラー関数を配列の各要素に適用できるようにする機能で、これも並列化に適しています。

再帰関数とPURE関数の組み合わせ

再帰関数を PURE として宣言することも可能です。これは、再帰的な計算が副作用を持たない場合に特に有効です。例えば、数学的な関数を再帰的に定義し、かつその計算過程で外部の状態を変更しない場合などです。

MODULE pure_recursive_mod IMPLICIT NONE
CONTAINS ! PUREかつRECURSIVEな階乗関数 PURE RECURSIVE FUNCTION pure_factorial(n) RESULT(res) INTEGER, INTENT(IN) :: n INTEGER :: res ! PURE関数内では PRINT や STOP は使えないので注意 ! エラー処理は呼び出し元で行うか、別の方法を検討 IF (n == 0) THEN res = 1 ELSE res = n * pure_factorial(n - 1) END IF END FUNCTION pure_factorial
END MODULE pure_recursive_mod
PROGRAM test_pure_recursive USE pure_recursive_mod IMPLICIT NONE INTEGER :: num = 6 PRINT *, 'Pure recursive factorial of', num, 'is', pure_factorial(num) ! 出力: Pure recursive factorial of 6 is 720
END PROGRAM test_pure_recursive 

PURE RECURSIVE 関数は、並列計算の文脈で再帰アルゴリズムを使用したい場合に役立ちます。

まとめ

  • RECURSIVE: 関数やサブルーチンが自身を呼び出すことを許可します。複雑な問題を簡潔に記述できる場合がありますが、終了条件が不可欠です。
  • PURE: 手続き(関数またはサブルーチン)が副作用を持たないことを宣言します。これにより、コンパイラによる最適化や並列実行が容易になります。多くの制約がありますが、コードの安全性と再利用性を高めます。
  • これらは組み合わせて PURE RECURSIVE としても使用できます。

これらのモダンFortranの機能を活用することで、より信頼性が高く、効率的な科学技術計算プログラムを作成することができます。積極的に利用していきましょう!

参考情報

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です