1. 導入: なぜ「副作用なし」が重要なのか?
皆さん、こんにちは!数値計算エンジニアの皆さんにとって、プログラムの「正確さ」と「速さ」は常に重要な課題ですよね。特に、計算を速くするために「並列計算」を活用する場面も多いかと思います。
しかし、並列計算には注意が必要です。もし関数が予期せぬ形でプログラムの他の部分に影響を与えてしまうと、計算結果が不安定になったり、デバッグが非常に難しくなったりする「副作用」という問題が発生します。
Fortranには、この「副作用」をなくし、関数をより安全で信頼性の高いものにするための強力な機能があります。それが今回ご紹介するPURE属性です。PURE属性を使うことで、並列計算を安全に行い、バグの少ない、読みやすいコードを書くための土台を築くことができます。
2. 基礎知識: 「副作用」とは何か? PURE属性の役割
まずは「副作用」についてもう少し詳しく見ていきましょう。
「副作用」とは?
関数がその役割(引数を受け取って結果を返す)以外に、プログラムの外部の状態を変更してしまうことを「副作用」と呼びます。例えば、以下のようなケースが副作用にあたります。
- グローバル変数を変更する: 関数内で宣言されていない、プログラム全体で共有されている変数の値を勝手に変更してしまう。
- 引数の値を変更する: 入力として受け取ったはずの引数の値を、関数内で書き換えてしまう。
- ファイルや画面に出力する: 計算とは直接関係なく、ファイルに書き込んだり、画面にメッセージを表示したりする。
- 乱数生成器の状態を変更する: 乱数生成器は内部状態を持つため、通常は副作用を伴います。
このような副作用があると、関数の呼び出し順序によって結果が変わったり、並列計算で複数の関数が同時にグローバル変数を変更しようとして競合したりする問題が起こります。
INTENT(IN)とPURE属性
Fortranでは、引数が入力専用であることを示すためにINTENT(IN)属性を使います。これは「この引数は関数内で変更しませんよ」という宣言です。PURE属性は、このINTENT(IN)の考え方を関数全体に拡張したものと言えます。
PURE属性とは?
プロシージャ(関数またはサブルーチン)にPURE属性を付与すると、「このプロシージャは、いかなる副作用も持ちません」とコンパイラに厳格に保証します。具体的には、以下のことが禁止されます。
- INTENT(IN)以外の引数を変更すること(つまり、INTENT(OUT)やINTENT(INOUT)の引数はPUREプロシージャでは使えません)。
- グローバル変数を変更すること。
- ファイル入出力などのI/O操作を行うこと。
- 他の副作用を持つプロシージャを呼び出すこと。
これらの制約により、コンパイラはPUREプロシージャが独立して安全に実行できることを確認でき、特に並列処理の最適化に役立てることができます。
3. 実装/解決策: PURE属性の使い方
PURE属性を使うのはとても簡単です。`FUNCTION`または`SUBROUTINE`キーワードの前に`PURE`と記述するだけです。
pure function my_pure_function(arg1, arg2) result(res)
! ...
end function my_pure_function
pure subroutine my_pure_subroutine(arg1, arg2)
! ...
end subroutine my_pure_subroutine
PUREプロシージャ内で許されること
- INTENT(IN)属性を持つ引数を参照すること。
- プロシージャ内で宣言されたローカル変数を使用し、変更すること。
- 他のPURE属性を持つプロシージャを呼び出すこと。
- 計算結果を返すこと(関数の場合)。
PUREプロシージャ内で許されないこと(例)
- INTENT(IN)属性の引数を変更しようとすること。
- グローバル変数(モジュール変数など)の値を変更すること。
- `PRINT `, `WRITE`などの出力操作。
- `READ `, `OPEN`, `CLOSE`などのファイル操作。
- `ALLOCATE`, `DEALLOCATE`などのメモリ割り当て/解放(ただし、関数の結果として配列などを割り当てる場合は例外的に許されることもあります)。
もし、これらの禁止事項を破ってPURE属性を付けたプロシージャを作成しようとすると、コンパイラがエラーを報告してくれます。
4. サンプルプログラム: PURE属性を持つ関数の例
ここでは、数値計算でよくある「値の二乗を計算する」関数を例に、PURE属性の具体的な使い方を見てみましょう。
program PureFunctionExample
implicit none
real :: value_in ! 入力する値
real :: value_squared ! 二乗の結果
! 入力値を設定
value_in = 7.5
! pure関数を呼び出して二乗を計算
value_squared = calculate_square(value_in)
! 結果を表示
print , "元の値 : ", value_in
print , "二乗の結果: ", value_squared
contains
! PURE属性を持つ関数(副作用なしを保証)
pure function calculate_square(x) result(r)
real, intent(in) :: x ! 入力引数 x。INTENT(IN)により、関数内で変更されないことを保証。
real :: r ! 関数の結果を格納する変数
! xの二乗を計算して結果rに代入
r = x x
! 注意: この関数内で以下の操作はコンパイルエラーになります
! x = x + 1.0 ! INTENT(IN)の引数を変更しようとしている
! print , "内部処理" ! I/O操作を行っている
! global_var = 10.0 ! グローバル変数を変更しようとしている
end function calculate_square
end program PureFunctionExample
この例では、`calculate_square`関数は`PURE`属性を持ち、`x`は`INTENT(IN)`として宣言されています。これにより、コンパイラは`calculate_square`関数が`x`の値やプログラムの他の状態を一切変更しないことを保証します。もし誤って`x = x + 1.0`のような変更を試みると、コンパイルエラーが発生し、問題が早期に発見されます。
5. 応用・注意点: 現場で役立つ情報
PURE属性は、単にエラーを検出するだけでなく、より良いコードを書くための強力なツールです。
- 並列処理 (`DO CONCURRENT`) との相乗効果:
`PURE`関数は、各呼び出しが完全に独立しているため、Fortranの`DO CONCURRENT`構文などを用いた並列処理と非常に相性が良いです。コンパイラは`PURE`関数を安心して並列に実行できると判断し、パフォーマンスの向上に繋がります。数値計算では、大量のデータを独立して処理する場面が多いため、このメリットは非常に大きいです。
- デバッグとテストのしやすさ:
副作用がないということは、「同じ入力に対しては常に同じ出力が返る」ということを意味します。これにより、関数の動作が予測しやすくなり、バグが発生した場合の原因特定が格段に容易になります。また、単体テストも非常に書きやすくなります。
- コードの信頼性と可読性の向上:
`PURE`と宣言された関数を見れば、その関数が安心して使える、安定した部品であることが一目でわかります。これにより、コード全体の信頼性が向上し、他の開発者にとっても理解しやすいコードになります。
- すべての関数に`PURE`を付けるべきか?
もちろん、すべての関数に`PURE`を付けられるわけではありません。例えば、ログ出力を行う関数や、乱数生成器のように内部状態を変更する必要がある関数は、副作用を持つため`PURE`にはできません。しかし、可能な限り`PURE`関数として設計することで、コードの品質を向上させることができます。
PURE属性を理解し、適切に活用することは、数値計算の現場でより効率的で信頼性の高いFortranプログラムを開発するための第一歩となります。ぜひ皆さんのコードに取り入れてみてください!

コメント