9日目「関数・サブルーチン・モジュールを作ろう」
能書き
プログラムが長くなれば長くなるほど保守作業は困難になります。プログラムの一部を「サブルーチン」や「関数」として 本体から分離することで、より見やすく、手の加えやすい「応用の利く」プログラムになります。
program test !------------------------ ! Fortran template !------------------------ use bacs implicit none !--- main --- ... !--- internal procedures --- contains function f1(...) ... end function f1 subroutine sub1(...) ... end subroutine sub1 end program使い方は、こちらを復習して下さい。
Lesson 25. ユーザー定義関数
定義:関数の定義
contains以下で、
function 関数名([仮引数1],[仮引数2],...) 型 :: 関数名 [型 :: 仮引数名] 関数名 = {式} end function
学習
『sin(x)』のような「関数」を自分で作ることができます。実際の用途としては、
何度も出てくる式をまとめる
複雑な処理を本体から分離する
組み込み関数にない特殊関数を作る
などがあります。
まずは簡単な例。
複雑な処理を本体から分離する
組み込み関数にない特殊関数を作る
real(kind=double) :: a,b a = 1.0d0 b = 2.0d0 print *, "a+b^2 =", myFunc(b,a) contains !--- internal procedures --- function myFunc(a,b) implicit none real(kind=double) :: a, b, myFunc myFunc = a*a +b end function end programこのように、
containsとend programの間にもう一つプログラムを書く
(ただしprogramの代わりにfunction)
というのが基本的な考え方です。functionに特有の性質として、
『関数名』をfunction内では変数として扱う(要:型宣言)
end functionの時点で『関数名』に入っている値が結果として返ってくる
ことを覚えておきましょう。end functionの時点で『関数名』に入っている値が結果として返ってくる
学習:仮引数と局所変数
関数『function myFunc(a,b)』のかっこ中の変数『a,b』のことを、 「仮引数」と言います。「引数」は「ひきすう」と読む。
ここには、本体で使われたときにかっこの中に入っている値、
すなわち『b,a』の値(こちらを「実引数」と言う)が、「対応する順番で」入ります。「対応する名前の変数」ではありません!混乱しないように。
すなわち上のプログラムの実行結果は「a*a+b=3」ではなく
a+b^2 = 5となります。
ここで、
関数中で宣言された変数と、本文中の変数とは無関係(独立)
というのがポイントです。
このように、外部から直接見ることができず、
そのプログラム単位内だけで使われる変数のことを「局所(ローカル)変数」と言います。変数名の競合に気を遣わなくて良いので、 そのままコピーペーストで他のプログラムに使い回すことができます。
ただし『関数名』がかぶるとダメです。
カッコが配列とも紛らわしいので、「関数っぽい」名前を心がけましょう。
逆に
本文の変数は、関数中で宣言し直さなければそのまま使える
のですが、用途が限定される(普通の意味での「関数」ではなくなる)ので
関数内で使う変数は引数として受け渡す
のが基本だと思って下さい。問題:階乗の関数を作りなさい
そういうわけで、 「整数 n を受け取り、n! を実数で返す」関数を作ってみましょう。 >>解答Lesson 26. サブルーチン
定義:サブルーチンの定義
contains以下で、
subroutine サブルーチン名[(仮引数1,仮引数2,...)] [型, 引数授受特性 :: 仮引数名] {処理} end subroutine*引数授受特性
『intent(in)』 入力引数(上書き禁止)
『intent(out)』 出力引数(値読み込み禁止)
『intent(inout)』 入出力引数(読み書き自由)
入力と出力を明確に区別することで、人間にもコンパイラにもわかりやすくなります。
デフォルトはinout。ちなみにFortranの引数は参照(アドレス)渡しです。
学習
関数が「いくつかの引数を受け取り、1つの値を出す」のに対し、サブルーチンは「受け取った引数に対して処理を行う」もの(出力はいくつでも良い)です。
外部とやりとりする必要のある変数を引数にした、独立プログラム
と考えたほうがいいかもしれません。理屈の上では、すべての「処理」をサブルーチンに置き換えることが可能です。
極端な話、プログラム本体を丸ごとサブルーチンにしてしまったり、
1行1行をサブルーチンにすることも可能です(>>例)。
実際にサブルーチンを使うべき場面としては、
何度も出てくる処理をまとめる(一般的)
一連の処理をまとめて見やすくする
処理を論理的に分離する(上級)
などがあります。
一連の処理をまとめて見やすくする
処理を論理的に分離する(上級)
使いこなすには、センスが必要です。好みの問題とも言えます。
例題
「2つの変数の値を入れ替える処理」をサブルーチンにします。real(kind=double) :: x, y x = zero y = one call swap(x,y) print *, "x =", x, ", y =", y call swap(x,y) print *, "x =", x, ", y =", y !--- internal procedures --- contains subroutine swap(a,b) implicit none real(kind=double), intent(inout) :: a, b real(kind=double) :: temp temp = a a = b b = temp end subroutine swap
x = 1.0000000000, y = 0.0000000000 x = 0.0000000000, y = 1.0000000000この場合、
同じ事を2回書くより簡潔で、修正・デバッグが簡単になった
「3つの代入文」が、明確な役割を持つ「1つの処理」になった
本体と無関係な一時変数『temp』を本体から分離した
のがポイントです。
「3つの代入文」が、明確な役割を持つ「1つの処理」になった
本体と無関係な一時変数『temp』を本体から分離した
ちなみに、「代入文3つで値を入れ替える」というのは定石です。覚えておきましょう。
Lesson 27. モジュール
定義:モジュールの作成
本体とは別のファイル"モジュール名.f90"を作り、module モジュール名 [use 他のモジュール] implicit none [変数の宣言] contains [subroutineやfunction] end moduleという形式にする。
混乱を避けるため、ファイル名とモジュール名は揃えておいた方が良い。
モジュール本体(containsの上)に具体的な命令を書き込むことはできない
また、
相互参照するモジュールは作れない
3つ以上で循環参照もダメ。上下関係の厳しい世界なのだ。
モジュールのコンパイル
以下の3通りから、好きな方法でやると良い。1.通常のコンパイル(堅実だがコンパイルに時間が掛かる)
$ ifort モジュール名.f90 本体.f90
モジュールを左に書くこと。複数ある場合は、use「される」モジュールをuse「する」モジュールより左側に書く。
2.分割コンパイル(一手間多いがコンパイルが速い)
はじめに一回だけ
$ ifort -c モジュール名.f90
$ ifort モジュール名.o 本体.f90本体を書き換える度に(書き換えられていない)モジュールをコンパイルし直す必要がないのが利点。
モジュールを書き換えた場合は、もちろんモジュールからコンパイルし直して下さい。
3.make(本格的に計算するなら)
makefileという一種のスクリプトを書き、コンパイルする。
$ makeモジュールの数が多くなったらこれに頼るべき。(makeの説明)
学習:モジュールの役割
モジュールそのものはただの「器」に過ぎません。中に何を入れるかによって、その役割が変わってきます。 例えば以下のような使い方が出来ます。・共通の定数をまとめて置いておく
大槻先生の"KindNumbers"、私の"bacs"がこれにあたります。
・汎用的な(他のプログラムでも使える)ルーチンを独立させる 上の例の『swap』をモジュールに入れるとこれです。
・汎用的な機能の集合体(パッケージ) 私の"ranpack"などがこれです。
・プログラム本体から分離された関数、サブルーチンを、別ファイルにする 単に本体が長くなるのが嫌なときに。
・共通の変数をまとめて置いておく いわゆるグローバル変数。私としては非推奨。
ある程度のプログラマになると、モジュールを使ってプログラムをうまく設計することが求められます。
が、皆さんは「サブルーチンの置き場所」くらいにとらえておけば十分です。
学習:副プログラムのパターン
実際に副プログラムを使う際の構成としては、(1)本体の『contains』以下に、関数やサブルーチンを書き込んで使う(単一ファイル)
(2)新しくモジュールを作り、その『contains』以下に書き込む。本体でモジュールを『use』して使う(複数ファイル)
の2つが考えられます。まずは(1)で作り、必要な場合だけ(2)の形にするのが良いでしょう。
モジュールにすると、変数の扱いに多少注意が必要です。
*注意
古いFORTRANの慣習では、全ての関数、サブルーチンは「外部手続き」として単独で別ファイルにすることになっています。
複数ファイル(ルーチン)をまとめた「サブルーチン群」になっている場合もあります。清水研で使われていた"rironken.for"など。
が、Fortran90の仕様上、この方法は非推奨です(>>例)。
Fortran90(以降)世代の皆さんは、作ったサブルーチンを
必ず『program』か『module』の『contains』に入れるようにしましょう。
他人からもらったサブルーチンも、モジュールに入れてから使うようにした方が安全です。
実践:モジュール化の例
いつか見たプログラムをモジュールを使って書くと以下のようになる。"hontai.f90"
program hontai use bacs use module1 implicit none complex(kind=double), allocatable :: Amat(:,:), Bmat(:,:), Cmat(:,:) integer :: m,n m = 2 n = 3 allocate(Amat(m,n), Bmat(m,n), Cmat(m,m)) ! --- define matrices --- 注:ここの値は適当に決める。 Amat(:,:) = cOne ! cOne = (1.0d0, 0.0d0) をAmatの全ての要素に代入 Bmat(:,:) = cZero ! cZero = (0.0d0, 0.0d0) をBmatの全ての要素に代入 Bmat(1,1:n) = img ! img = (0.0d0, 1.0d0) を1行目に入れる call matpro_ND(m,n,Amat,Bmat,Cmat) ! --- print C matrix --- ! ループを使ってうまく表示してみよう。 print *, "Real part of C matrix" print *, dble(Cmat) ! 実数部分を(倍精度で)取り出す print *, "Imagnary part of C matrix" print *, aImag(Cmat) ! 虚数部分を取り出す end program"module1.f90"
program module1 use bacs implicit none contains subroutine matpro_ND(m,n,A,B,C) implicit none integer, intent(in) :: m, n complex(kind=double), intent(in) :: A(m,n), B(m,n) complex(kind=double), intent(out) :: C(m,m) integer :: j,k Cmat(:,:) = cZero do j=1,m do k=1,n Cmat(1:m,j) = Cmat(1:m,j) + Amat(1:m,k)*conjg(Bmat(j,k)) end do end do end subroutine end module今、サブルーチン『matpro_ND』は純粋に数学的な処理なので、本体側でどのような問題を扱っているかに依存しない。
こういう場合はモジュールとして分離するのが望ましい。
謝辞
Fortran実用編はここまでです。プログラミング言語としてのFortranは、ほぼ学び終えたと考えて良いでしょう。 あとは実際に自分の手でプログラムを組んでみて、技術としてのプログラミングを学ぶだけです。
わからないことが出てきたら、ネットで調べるなり人に聞くなりしましょう。
基礎が出来ていれば、何が分からないか・何を調べればよいか自分でわかりますね。
また、私(著者)に質問すれば、その内容がこの講義ノートにも反映されることになります。遠慮無く質問して下さい。ちなみに・・・プロの計算屋を目指すなら、より高度な技法を身につける必要があります。
もし希望するのであれば、個人的に修行をつけます。ご相談下さい。