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

Fortran

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

はじめに ✨

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

参考情報 📚

コメント

タイトルとURLをコピーしました