コンテンツにスキップ

Fortran/サブルーチンと関数

出典: フリー教科書『ウィキブックス(Wikibooks)』

関数とサブルーチン

[編集]

ほとんどのプログラムでは、同じブロックのコードが複数の場所で再利用されることがよくあります。コードの重複を最小限に抑え、コードのメンテナンスを容易にするためには、そのようなコードのブロックは関数またはサブルーチン内に配置する必要があります。Fortranのfunctionは、数学的な関数に似ており、0個以上の引数を入力として受け取り、単一の出力値を返します。Fortranのsubroutineは、入力変数に対していくつかの操作を行うコードブロックであり、サブルーチンを呼び出すことで入力変数が変更されます。

関数呼び出しを含む式:
! func1は他の場所で定義された関数です。 
! それは整数を入力として受け取り、別の整数を出力として返します。 
a = func1(b)
サブルーチンの呼び出し:
! sub1は他の場所で定義されたサブルーチンです。 
! sub1は入力変数eとfに対していくつかの操作を行います。 
call sub1(e, f) 
! 今、eまたはf、またはその両方(またはどちらも)が変更されている可能性があります。

多くのプログラミング言語では、関数とサブルーチンを区別しません(例:C/C++PythonRustGo)。純粋な関数型プログラミング言語(例: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

引数iintent (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手続きとして指定する必要があります。 externalimplicitインターフェイスのみを提供します。これは、コンパイラが引数の数やデータ型を把握していないため、コンパイル時に警告を生成できないことを意味します(モジュール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
訳註
上記のコードで関数 squarepure と宣言されているので、同じ値を引数に渡されると必ず同じ値が返る事が保証されます。この事から
    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