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

引数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内でどのように参照されるかです。この場合、戻り変数は関数自体と同じ名前を持っています。

再帰呼び出し編集

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 として指定することで含まれなければなりません。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 で強制することができます。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ブロックは、独自のスコープを持っていることに注意してください。したがって、外部の値にアクセスする必要がある場合は、明示的にロードする必要があります。これは、importuse文で実現できます。

その例を以下に示します。

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