[Fortranのはじめ方] Part22: 派生型と演算子のオーバーロード

はじめに

これまでのステップで、Fortranの基本的なデータ型(整数、実数、文字など)を学んできました。しかし、実際の科学技術計算では、もっと複雑なデータ構造を扱いたい場面が多くあります。例えば、点の座標 (x, y) や、物質の物理的性質(密度、温度、圧力など)をひとまとめにして扱いたい場合です。

モダンFortranでは、このような独自データ構造を定義するために派生型 (Derived Type) という機能が用意されています。さらに、定義した派生型に対して、既存の演算子(+, *, == など)が独自の意味を持つように演算子をオーバーロード(多重定義)することも可能です。これにより、あたかも組み込み型のように自然な形で独自のデータ型を操作でき、コードの可読性と直感性が大幅に向上します 。

このセクションでは、派生型の定義方法と使い方、そして演算子のオーバーロードについて学び、より柔軟で表現力豊かなプログラミングを目指しましょう!

派生型 (Derived Type)

派生型は、異なる型の変数をひとまとめにした、ユーザー定義のデータ構造です。C言語の構造体 (struct) や他のオブジェクト指向言語のクラスのデータメンバに似ています。

派生型の定義

派生型は TYPE 文と END TYPE 文で囲んで定義します。内部には、その派生型が持つコンポーネント(構成要素)を通常の変数宣言と同じように記述します。

TYPE :: type_name component_definition_statement component_definition_statement ...
END TYPE type_name 

例えば、2次元座標を表す派生型 Point を定義してみましょう。

MODULE geometry_mod IMPLICIT NONE ! 2次元座標を表す派生型 TYPE :: point_t REAL :: x = 0.0 ! x座標 (初期値設定) REAL :: y = 0.0 ! y座標 (初期値設定) END TYPE point_t
CONTAINS ! ... (ここに関数やサブルーチンを定義できる)
END MODULE geometry_mod 

この例では、point_t という名前の派生型を定義し、xy という実数型のコンポーネントを持たせています。Fortran 2003以降では、コンポーネントにデフォルトの初期値を設定できます。また、派生型の定義はモジュール内に記述するのが一般的です。これにより、関連するデータ型や手続きをまとめ、再利用しやすくなります。

派生型変数の宣言と利用

定義した派生型を使って変数を宣言するには、TYPE(type_name) のように記述します。

PROGRAM derived_type_example USE geometry_mod ! 派生型定義を含むモジュールをUSE IMPLICIT NONE TYPE(point_t) :: p1, p2 ! point_t型の変数を宣言 REAL :: distance ! コンポーネントへのアクセスと代入 ( % 演算子を使用) p1%x = 1.0 p1%y = 2.0 ! 構造体コンストラクタによる初期化 p2 = point_t(3.0, 4.0) ! 定義された順序で値を指定 WRITE(*,*) 'Point 1: (', p1%x, ', ', p1%y, ')' WRITE(*,*) 'Point 2: (', p2%x, ', ', p2%y, ')' ! コンポーネントを使った計算 distance = SQRT((p2%x - p1%x)**2 + (p2%y - p1%y)**2) WRITE(*,*) 'Distance:', distance
END PROGRAM derived_type_example 

派生型のコンポーネントにアクセスするには、変数名の後にパーセント記号 % をつけてコンポーネント名を指定します (例: p1%x)。

また、type_name(value1, value2, ...) の形式で値を指定して、派生型変数を初期化または代入できます。これを構造体コンストラクタと呼びます。コンポーネントの定義順に値を指定します。

演算子のオーバーロード

演算子のオーバーロード(多重定義)は、既存の演算子(+, -, *, /, ==, /=, <, <=, >, >= など)や代入(=)に、ユーザー定義の派生型に対する独自の操作を割り当てる機能です。これにより、派生型をあたかも組み込み型のように自然に扱えるようになります。

オーバーロードの定義方法

演算子のオーバーロードは、モジュール内で INTERFACE OPERATOR(...) または INTERFACE ASSIGNMENT(=) を使って定義します。インターフェースブロック内には、その演算に対応する関数(またはサブルーチン)を指定します。

定義済み演算子のオーバーロード:

INTERFACE OPERATOR(.operator.) MODULE PROCEDURE function_name_1, function_name_2, ...
END INTERFACE OPERATOR(.operator.) 

ここで .operator. はオーバーロードしたい演算子(例: +, *, == など)です。function_name_1 などは、その演算を実行する関数名を指定します。

代入演算子 (=) のオーバーロード:

INTERFACE ASSIGNMENT(=) MODULE PROCEDURE subroutine_name_1, subroutine_name_2, ...
END INTERFACE ASSIGNMENT(=) 

代入演算子のオーバーロードには、サブルーチンを使用します。

具体例: 座標の加算と等価比較

先ほどの point_t 型に対して、2つの点の加算(ベクトル和)を行う + 演算子と、2つの点が等しいか比較する == 演算子をオーバーロードしてみましょう。これらの定義はモジュール内に記述します。

MODULE geometry_mod IMPLICIT NONE ! 派生型の定義 (再掲) TYPE :: point_t REAL :: x = 0.0 REAL :: y = 0.0 END TYPE point_t ! 公開するインターフェースを宣言 PUBLIC :: point_t ! 派生型自体 PUBLIC :: OPERATOR(+) ! + 演算子のオーバーロード PUBLIC :: OPERATOR(==) ! == 演算子のオーバーロード ! 演算子オーバーロードのインターフェース定義 INTERFACE OPERATOR(+) MODULE PROCEDURE add_points END INTERFACE OPERATOR(+) INTERFACE OPERATOR(==) MODULE PROCEDURE are_points_equal END INTERFACE OPERATOR(==)
CONTAINS ! '+' 演算子に対応する関数 (点同士の加算) FUNCTION add_points(p1, p2) RESULT(p_sum) TYPE(point_t), INTENT(IN) :: p1, p2 ! 入力: 2つの点 TYPE(point_t) :: p_sum ! 出力: 加算結果の点 p_sum%x = p1%x + p2%x p_sum%y = p1%y + p2%y END FUNCTION add_points ! '==' 演算子に対応する関数 (点同士の比較) FUNCTION are_points_equal(p1, p2) RESULT(is_equal) TYPE(point_t), INTENT(IN) :: p1, p2 ! 入力: 2つの点 LOGICAL :: is_equal ! 出力: 比較結果 (真偽値) REAL, PARAMETER :: tolerance = 1.0E-6 ! 浮動小数点比較のための許容誤差 ! 誤差を考慮して比較 is_equal = (ABS(p1%x - p2%x) < tolerance) .AND. & (ABS(p1%y - p2%y) < tolerance) END FUNCTION are_points_equal
END MODULE geometry_mod 

オーバーロードされた演算子の利用

上記のようにモジュール内で演算子をオーバーロードすると、メインプログラムなど、そのモジュールを USE した箇所で、定義した派生型に対して自然な形で演算子を使えるようになります。

PROGRAM overload_example USE geometry_mod IMPLICIT NONE TYPE(point_t) :: pt_a, pt_b, pt_c, pt_d pt_a = point_t(1.0, 2.0) pt_b = point_t(3.0, 4.0) pt_d = point_t(1.0, 2.0) ! pt_aと同じ座標 ! オーバーロードされた '+' 演算子を使用 pt_c = pt_a + pt_b WRITE(*,'(A,F4.1,A,F4.1,A)') 'Point A: (', pt_a%x, ', ', pt_a%y, ')' WRITE(*,'(A,F4.1,A,F4.1,A)') 'Point B: (', pt_b%x, ', ', pt_b%y, ')' WRITE(*,'(A,F4.1,A,F4.1,A)') 'Point C (A+B): (', pt_c%x, ', ', pt_c%y, ')' ! オーバーロードされた '==' 演算子を使用 IF (pt_a == pt_b) THEN WRITE(*,*) 'Point A is equal to Point B.' ELSE WRITE(*,*) 'Point A is NOT equal to Point B.' END IF IF (pt_a == pt_d) THEN WRITE(*,*) 'Point A is equal to Point D.' ELSE WRITE(*,*) 'Point A is NOT equal to Point D.' END IF
END PROGRAM overload_example 

実行すると、pt_c には pt_apt_b の各要素が足し合わされた結果 (4.0, 6.0) が格納され、pt_a == pt_d は真 (true) と評価されます。このように、あたかも組み込み型のように +== 演算子を使えるのがオーバーロードの大きなメリットです。

注意点

  • オーバーロードする関数は、引数の数や型が演算子の意味に合うように定義する必要があります(例: 二項演算子は引数2つ、単項演算子は引数1つ)。
  • == 演算子をオーバーロードする場合、通常 /= 演算子もセットでオーバーロードすることが推奨されます。
  • 浮動小数点数の比較では、完全一致 (==) ではなく、ある程度の許容誤差 (tolerance) を設けるのが一般的です。
  • 代入演算子 = のオーバーロードは、より複雑な派生型(例えばポインタコンポーネントを含む場合など)で深いコピーを実現したい場合などに利用されますが、注意深く実装する必要があります。

まとめ

このセクションでは、モダンFortranの強力な機能である派生型演算子のオーバーロードについて学びました。

  • 派生型 (Derived Type): TYPE ... END TYPE で独自のデータ構造を定義でき、% 演算子でコンポーネントにアクセスします。構造体コンストラクタによる初期化も可能です。
  • 演算子のオーバーロード: INTERFACE OPERATOR(...)INTERFACE ASSIGNMENT(=) を用いて、既存の演算子に派生型に対する独自の操作を定義できます。これにより、コードの可読性と直感性が向上します。
  • モジュールの活用: 派生型の定義や演算子のオーバーロードは、モジュール内に記述することで、カプセル化され再利用性が高まります。

これらの機能を活用することで、複雑な問題をより抽象的かつ直感的にモデル化し、読みやすく保守しやすいコードを書くことができます。特に大規模な科学技術計算プログラムにおいて、これらの機能は非常に有効です。

次のステップでは、実際の数値計算応用例として、行列計算ライブラリとの連携などを見ていきましょう!

参考情報

コメントを残す

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