Fortran/サブルーチンと関数
関数とサブルーチン 編集
多くのプログラムでは、1つのコードブロックが複数の場所で再利用されることがよくあります。コードの重複を最小限に抑え、コードのメンテナンスを容易にするために、このようなコードブロックは、関数やサブルーチンの中に配置する必要があります。Fortranのfunction
は、数学の関数に似ており、1つまたは複数のパラメータを入力として受け取り、1つの出力値を返します。Fortranのsubroutine
は、入力変数に対して何らかの操作を行うコードのブロックであり、サブルーチンを呼び出した結果、入力変数が変更されます。
関数呼び出しを含む式:
! func1 is a function defined elsewhere.
! It takes an integer as an input and returns another integer as the output.
a = func1(b)
サブルーチンの呼び出し:
! sub1 is a subroutine defined elsewhere.
! sub1 performs some operation on input variables e and f.
call sub1(e, f)
! Now e or f, or both (or neither) may be modified.
多くのプログラミング言語では、関数とサブルーチンを区別していません(C/C++、Python、Javaなど)。純粋な関数型プログラミング言語(Haskellなど)では、関数しか認めていません。サブルーチンは、場合によっては副作用として入力変数を変更することがあり、コードが複雑になってしまうからです。
関数はサブルーチンよりも単純です。関数は単一の値を返さなければならず、write
文のような式の中や、if文中の条件式if (function) then
などから呼び出すことができます。サブルーチンは値を返しませんが、引数を介して多くの値を返すことができ、独立したコマンドとしてのみ使用できます(キーワードcall
を使用)。
関数 編集
Fortranでは、関数
を使って、値や値の配列を返すことができます。次のプログラムは,整数の2乗と3乗の和を計算する関数を呼び出しています。
function func(i) result(j)
integer, intent (in) :: i ! input
integer :: j ! output
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
内でどのように参照されるかです。この場合、戻り変数は関数自体と同じ名前を持っています。
再帰呼び出し 編集
Fortranでは、再帰的な階乗関数のような再帰関数を含んだコードをコンパイルするためには、再帰宣言をする必要があります。
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
サブルーチン 編集
サブルーチン
は、引数を通して複数の値を返すことができる。これは、call
文で呼び出されます。以下はその例です。
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
program main
implicit none
external square_cube ! external subroutine
integer :: isq, icub
call square_cube(4, isq, icub)
print *, "i,i^2,i^3=", 4, isq, icub
end program
インテント 編集
関数やサブルーチンの内部で変数を宣言する際に、その変数の出入りをチェックするためにインテントを付加することができます。 デフォルトではインテントチェックは行われません。これにより、誤ったコーディングがコンパイラに検出されない可能性があります。
intent (in)
- 仮引数の値はプロシージャ内で使用できますが、変更はできません。intent (out)
- 仮引数の値は手続き内で設定、修正し、その値を呼び出し側に返すことができる。intent (inout)
- 仮引数の初期値はプロシージャ内で使用と修正の両方が可能で、その値は呼び出し元に返されます。
関数とサブルーチンの違いについて 編集
関数の結果の定義の違い 編集
関数は、その結果のデータ型を、独立した変数として、あるいは関数名で異なる形で定義することができます。
以下に例を示します。
function f1(i) result (j)
!! result's variable: separately specified
!! result's data type: separately specified
integer, intent (in) :: i
integer :: j
j = i + 1
end function
integer function f2(i) result (j)
!! result's variable: separately specified
!! result's data type: by prefix
integer, intent (in) :: i
j = i + 2
end function
integer function f3(i)
!! result's variable: by function name
!! result's data type: by prefix
integer, intent(in) :: i
f3 = i + 3
end function
function f4(i)
!! result's variable: by function name
!! result's data type: separately specified
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) ! output: 1
print *, 'f2(0)', f2(0) ! output: 2
print *, 'f3(0)', f3(0) ! output: 3
print *, 'f4(0)', f4(0) ! output: 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
で強制することができます。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
interfaceブロック 編集
手続きが他の手続きを仮引数に持つ場合、他のパラメータの型と同様に、その型を指定しなければなりません。このような場合には interface
ブロックが使われます。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
関数のように、異なる入力引数に対して同じ名前のジェネリック関数を作成することができます。
以下の例では、2つの整数または文字列を加算する関数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
遅延 (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