Fortran/サブルーチンと関数
関数とサブルーチン
[編集]ほとんどのプログラムでは、同じブロックのコードが複数の場所で再利用されることがよくあります。コードの重複を最小限に抑え、コードのメンテナンスを容易にするためには、そのようなコードのブロックは関数またはサブルーチン内に配置する必要があります。Fortranのfunction
は、数学的な関数に似ており、0個以上の引数を入力として受け取り、単一の出力値を返します。Fortranのsubroutine
は、入力変数に対していくつかの操作を行うコードブロックであり、サブルーチンを呼び出すことで入力変数が変更されます。
- 関数呼び出しを含む式:
! func1は他の場所で定義された関数です。 ! それは整数を入力として受け取り、別の整数を出力として返します。 a = func1(b)
- サブルーチンの呼び出し:
! sub1は他の場所で定義されたサブルーチンです。 ! sub1は入力変数eとfに対していくつかの操作を行います。 call sub1(e, f) ! 今、eまたはf、またはその両方(またはどちらも)が変更されている可能性があります。
多くのプログラミング言語では、関数とサブルーチンを区別しません(例:C/C++、Python、Rust、Go)。純粋な関数型プログラミング言語(例:Haskell)では、サブルーチンは場合によっては入力変数を副作用として変更できるため、コードを複雑にする可能性があるため、関数のみが許可されます。
関数はサブルーチンよりもシンプルです。関数は単一の値を返さなければならず、write
文、if (function) then
内の式などの式内から呼び出すことができます。サブルーチンは値を返さず、その引数を介して多くの値を返すことができ、call
キーワードを使用して単独のコマンドとしてのみ使用できます。
関数
[編集]Fortranでは、function
を使用して値または値の配列を返すことができます。次のプログラムは、整数の自乗と立方の和を計算するために関数を呼び出します。
function func(i) result(j) integer, intent (in) :: i ! 入力 integer :: j ! 出力 j = i**2 + i**3 end function program main implicit none integer :: i integer :: func i = 3 print *, "sum of the square and cube of", i, "is", func(i) end program
引数i
のintent (in)
属性は、i
が関数内で変更されないことを意味し、それに対して戻り値j
は自動的にintent (out)
を持ちます。 func
の戻り値の型を宣言する必要があることに注意してください。これを省略すると、一部のコンパイラではコンパイルされません。 Open64(訳註:Itaniumおよびx86-64マイクロプロセッサアーキテクチャ向けの無料のオープンソースの最適化コンパイラ)は警告を伴って生成されたコードをコンパイルしますが、動作は定義されていません。
別の形式(F77互換)は次のとおりです。
FUNCTION func_name(a, b)
INTEGER :: func_name
INTEGER :: a
REAL :: b
func_name = (2*a)+b
RETURN
END FUNCTION
PROGRAM cows
IMPLICIT NONE
INTEGER :: func_name
PRINT *, func_name(2, 1.3)
END PROGRAM
func_name
の戻り値の型も、上記と同様に宣言する必要があります。唯一の違いは、func_name
内でfunc_name
の戻り値の型が参照される方法です。この場合、戻り値変数は関数自体と同じ名前を持っています。
再帰呼び出し
[編集]再帰関数は、以下に示す方法で宣言でき、コードをコンパイルできるようになります。
recursive function fact(i) result(j) integer, intent (in) :: i integer :: j if (i==1) then j = 1 else j = i * fact(i - 1) end if end function fact
サブルーチン
[編集]subroutine
は、その引数を介して複数の値を返すために使用できます。これはcall
文で呼び出されます。以下はその例です。
subroutine square_cube(i, isquare, icube) integer, intent (in) :: i ! 入力 integer, intent (out) :: isquare, icube ! 出力 isquare = i**2 icube = i**3 end subroutine program main implicit none external square_cube ! 外部サブルーチン integer :: isq, icub call square_cube(4, isq, icub) print *, "i,i^2,i^3=", 4, isq, icub end program
インテント
[編集]関数とサブルーチン内で宣言される変数に対して、渡す必要がある場合はインテント(Intent; 意図)を追加することができます。 デフォルトでは、インテントのチェックは行われず、コンパイラによって誤ったコーディングが検出されない可能性があります。
intent (in)
- ダミー引数の値は手続き内で使用されますが、変更されません。intent (out)
- ダミー引数は手続き内で設定され、その後変更され、値が呼び出し元に返されます。intent (inout)
- ダミー引数の初期値は手続き内で使用および変更され、その後呼び出し元に返されます。
関数とサブルーチンのさらなる比較
[編集]異なる関数結果の定義
[編集]関数は、結果のデータ型を別々の変数または関数名で定義できます。
以下に例を示します
function f1(i) result (j) !! 結果の変数:別々に指定された !! 結果のデータ型:別々に指定された integer, intent (in) :: i integer :: j j = i + 1 end function integer function f2(i) result (j) !! 結果の変数:別々に指定された !! 結果のデータ型:プレフィックスで指定された integer, intent (in) :: i j = i + 2 end function integer function f3(i) !! 結果の変数:関数名で指定 !! 結果のデータ型:プレフィックスで指定された integer, intent(in) :: i f3 = i + 3 end function function f4(i) !! 結果の変数:関数名で指定 !! 結果のデータ型:別々に指定された integer, intent (in) :: i integer :: f4 f4 = i + 4 end function program main implicit none integer :: f1, f2, f3, f4 print *, 'f1(0)', f1(0) ! 出力: 1 print *, 'f2(0)', f2(0) ! 出力: 2 print *, 'f3(0)', f3(0) ! 出力: 3 print *, 'f4(0)', f4(0) ! 出力: 4 end program
外部手続き
[編集]手続きは、モジュールuse
で含めるか、external
手続きとして指定する必要があります。 external
はimplicitインターフェイスのみを提供します。これは、コンパイラが引数の数やデータ型を把握していないため、コンパイル時に警告を生成できないことを意味します(モジュールuse
からの明示的インターフェイスとは対照的です。参照: Fortran/オブジェクト指向プログラミング)。
subroutine square_cube(i, isquare, icube) integer, intent (in) :: i ! input integer, intent (out) :: isquare, icube ! output isquare = i**2 icube = i**3 end subroutine integer function pow4(i) integer, intent (in) :: i pow4 = i**4 end function program main implicit none external square_cube ! external subroutine (only implicit interface) integer :: pow4 ! external function (only implicit interface) integer :: i, isq, icub i = 5 call square_cube(i, isq, icub) print '(A,4I5)', "i,i^2,i^3,i^4=", i, isq, icub, pow4(i) end program
純粋な手続き
[編集]関数とサブルーチンは、入力変数を変更できます。サブルーチンは出力値を返さないため、必然的に入力変数を変更します。関数はそうする必要はありませんが、デフォルトでは入力変数を変更することが許可されます。関数は、すべての入力変数にintent
属性を使用し、さらにpure
キーワードを使用して、いかなる副作用(side-effects)も持たない純粋な関数に変換できます。 pure
キーワードは、実質的に関数がいかなる副作用も持たないようにする追加の制限を課します。
pure
関数の例を示します。
pure real function square(x) real, intent (in) :: x square = x*x end function program main real :: a, b, square a = 2.0 b = square(a) ! After invoking the square(.) pure function, we can be sure that ! besides assigning the output value of square(a) to b, ! nothing else has been changed. end program
- 訳註
- 上記のコードで関数
square
はpure
と宣言されているので、同じ値を引数に渡されると必ず同じ値が返る事が保証されます。この事から b = square(a) + square(a) + square(a)
を
b = 3 * square(a)
にコンパイラにより最適化することが可能となります。
キーワード引数
[編集]ダミー名で引数を指定すると、入力引数の順序を任意に指定できます。それは、呼び出し手続きが意図された手続きのインターフェイスブロックを持っている場合(module
を使用すると自動的に作成される)、可能です。
また、一部のパラメーターを位置で、残りのパラメーターをダミー名で指定するハイブリッドメソッドもあります。
次に例を示します。
real function adder(a,b,c,d) real, intent (in) :: a, b, c, d adder = a+b+c+d end function program main interface real function adder(a,b,c,d) real, intent (in) :: a, b, c, d end function end interface print *, adder(d=1.0, b=2.0, c=1.0, a=1.0) ! specify each parameter by dummy name print *, adder(1.0, d=1.0, b=2.0, c=1.0) ! specify some parameters by dummy names, other by position end program
オプションの引数
[編集]引数はoptional
として設定することができます。 組み込み関数present
を使用して、特定のパラメーターが設定されているかどうかを確認できます。
以下に例を示します。
real function tester(a) real, intent (in), optional :: a if (present(a)) then tester = a else tester = 0.0 end if end function program main interface function tester(a) real function tester(a) real, intent (in), optional :: a end function end interface print *, "[no args] tester() :", tester() ! yields: 0.0 print *, "[ args] tester(1.0):", tester(1.0) ! yields: 1.0 end program
インターフェイスブロック
[編集]手続きがダミー引数として別の手続きを持っている場合、その型を指定する必要があります。これは、procedure
文とその引数の定義からなるinterface
ブロックを使用して行われます。
注意:各interface
ブロックには独自のスコープがあります。そのため、外部の値にアクセスする必要がある場合は、明示的にそれらを読み込む必要があります。これは、import
またはuse
文によって実現できます。
以下に例を示します。
function tester(a) real, intent (in) :: a real :: tester tester = 2*a + 3 end function tester program main interface function tester(a) real, intent (in) :: a real :: tester end function tester end interface print *, "tester(1.0):", tester(1.0) ! yields: 5.0 end program main
Save属性
[編集]変数の値を手続き呼び出しの間に保存するには、save
属性を明示的に指定します。
以下に例を示します。
subroutine f() implicit none integer, save :: i = 0 i = i + 1 print *, "value i:", i end program main implicit none interface subroutine f() integer, save :: i = 0 end end interface call f() ! yields: 1 call f() ! yields: 2 call f() ! yields: 3 end program main
ジェネリック関数
[編集]整数、実数、複素データ型に対して機能するabs
関数のように、異なる入力引数のために同じ名前のジェネリック関数を作成することができます。
以下の例は、整数または文字列を追加するadd
関数を作成する方法を示しています。
module add_mod implicit none private public :: add interface add procedure add_int, add_char end interface add contains pure function add_int( x, y ) integer, intent (in) :: x, y integer :: add_int add_int = x+y end function add_int pure function add_char( x, y ) character (len=*), intent (in) :: x, y character (len=len(x)+len(y)), allocatable :: add_char add_char = x // y end function add_char end module add_mod program main use add_mod implicit none print *, "add ints: ", add( 1, 2 ) print *, "add chars: ", add("abc", "def") end program main
- 実行結果
add ints: 3 add chars: abcdef
遅延 (Deferred)
[編集]抽象型のタイプバウンド手続きを作成するには、deferred
遅延属性を指定する必要があります。これにより、型の実際のバインディングが行われるまで、型バウンド手続きは実際には作成されません。これにより、型のバインディングポイントを決定するために、プログラムの実行時にタイプバウンド手続きを選択する柔軟性が向上します。
詳しくはFortran/オブジェクト指向プログラミング#抽象型の項を参照してください。
要素別処理手続 (Elemental)
[編集]要素は任意の次元のパラメーターを操作する手続きを作成することができます。キーワード elemental
は、単一のオブジェクト(例:整数)に対する操作を定義する場合に使用され、一般的な場合は自動的に処理されます。
以下の例は、任意の長さの整数次元の加算が示されています。
- 任意の長さの整数次元を加算する例
- :
pure elemental function add_int(x, y) integer, intent(in) :: x, y integer :: add_int add_int = x + y end function add_int program main implicit none interface pure elemental function add_int(x, y) integer, intent(in) :: x, y integer :: add_int end function add_int end interface print *, "add ints:", add_int(1, 2) ! yields: 3 print *, "add arrays:", add_int([1, 2], [2, 3]) ! yields: 3 5 end program main
- 実行結果
add ints: 3 add arrays: 3 5