コールバックにおける型変換#

型変換を行う方法は基本的に5つあり、それぞれに長所と短所があります。

方法 I、II、および V は C と Fortran の両方で使用できます。方法 III および IV は Fortran でのみ使用できます。方法 VI は廃止されており、使用すべきではありません。

ワークアレイ#

呼び出し元が必要とするすべてをパックし、呼び出されたルーチンによってアンパックされる「ワークアレイ」を渡します。これは古い方法です。たとえば、LAPACK がそれを行う方法です。

インテグレーター

module integrals
  use types, only: dp
  implicit none
  private
  public simpson

contains

real(dp) function simpson(f, a, b, data) result(s)
  real(dp), intent(in) :: a, b
  interface
    real(dp) function func(x, data)
    use types, only: dp
    implicit none
    real(dp), intent(in) :: x
    real(dp), intent(inout) :: data(:)
    end function
  end interface
  procedure(func) :: f
  real(dp), intent(inout) :: data(:)
  s = (b-a) / 6 * (f(a, data) + 4*f((a+b)/2, data) + f(b, data))
end function

end module

使い方

module test
  use types, only: dp
  use integrals, only: simpson
  implicit none
  private
  public foo

contains

real(dp) function f(x, data) result(y)
  real(dp), intent(in) :: x
  real(dp), intent(inout) :: data(:)
  real(dp) :: a, k
  a = data(1)
  k = data(2)
  y = a*sin(k*x)
end function

subroutine foo(a, k)
  real(dp) :: a, k
  real(dp) :: data(2)
  data(1) = a
  data(2) = k
  print *, simpson(f, 0._dp, pi, data)
  print *, simpson(f, 0._dp, 2*pi, data)
end subroutine

end module

一般的な構造#

実際に必要な(または将来必要になる可能性のある)バリエーションを包含する一般的な構造を定義します。この単一の構造型は、将来のニーズやアイデアに合わせて必要に応じて変更できますが、たとえば、実数を渡すことから、たとえば、テキストエディタのインスタンス化に変わることはおそらくないでしょう。

インテグレーター

module integrals
  use types, only: dp
  implicit none
  private
  public simpson, context

  type context
    ! This would be adjusted according to the problem to be solved.
    ! For example:
    real(dp) :: a, b, c, d
    integer :: i, j, k, l
    real(dp), pointer :: x(:), y(:)
    integer, pointer :: z(:)
  end type

contains

real(dp) function simpson(f, a, b, data) result(s)
  real(dp), intent(in) :: a, b
  interface
    real(dp) function func(x, data)
    use types, only: dp
    implicit none
    real(dp), intent(in) :: x
    type(context), intent(inout) :: data
    end function
  end interface
  procedure(func) :: f
  type(context), intent(inout) :: data
  s = (b-a) / 6 * (f(a, data) + 4*f((a+b)/2, data) + f(b, data))
end function

end module

使い方

module test
  use types, only: dp
  use integrals, only: simpson, context
  implicit none
  private
  public foo

contains

real(dp) function f(x, data) result(y)
  real(dp), intent(in) :: x
  type(context), intent(inout) :: data
  real(dp) :: a, k
  a = data%a
  k = data%b
  y = a*sin(k*x)
end function

subroutine foo(a, k)
  real(dp) :: a, k
  type(context) :: data
  data%a = a
  data%b = k
  print *, simpson(f, 0._dp, pi, data)
  print *, simpson(f, 0._dp, 2*pi, data)
end subroutine

end module

本当に必要な柔軟性はそれほど多くありません。たとえば、この目的のために 2 つの構造型を定義できます。1 つは Schroedinger 用、もう 1 つは Dirac 用です。それぞれが十分に一般的であり、必要なすべての部分を適切なラベルで含みます。

ポイントは、「すべてを包含する 1 つの抽象型」である必要はないということです。「すべて」と「なし」の間には、自然で実行可能なオプションがあります。

プライベートモジュール変数#

モジュール変数に渡すことで、可変引数を完全に隠します。

インテグレーター

module integrals
  use types, only: dp
  implicit none
  private
  public simpson

contains

real(dp) function simpson(f, a, b) result(s)
  real(dp), intent(in) :: a, b
  interface
    real(dp) function func(x)
    use types, only: dp
    implicit none
    real(dp), intent(in) :: x
    end function
  end interface
  procedure(func) :: f
  s = (b-a) / 6 * (f(a) + 4*f((a+b)/2) + f(b))
end function

end module

使い方

module test
  use types, only: dp
  use integrals, only: simpson
  implicit none
  private
  public foo

  real(dp) :: global_a, global_k

contains

real(dp) function f(x) result(y)
  real(dp), intent(in) :: x
  y = global_a*sin(global_k*x)
end function

subroutine foo(a, k)
  real(dp) :: a, k
  global_a = a
  global_k = k
  print *, simpson(f, 0._dp, pi)
  print *, simpson(f, 0._dp, 2*pi)
end subroutine

end module

ただし、可能であれば、このようなグローバル変数(実際には準グローバルなだけですが)は避けるのが最善です。しかし、時にはそれが最もシンプルでクリーンな方法かもしれません。ただし、少し考えれば、通常、II または IV のように、より優れた、より安全で、より明示的な方法があります。

ネストされた関数#

インテグレーター

module integrals
  use types, only: dp
  implicit none
  private
  public simpson

contains

real(dp) function simpson(f, a, b) result(s)
  real(dp), intent(in) :: a, b
  interface
    real(dp) function func(x)
    use types, only: dp
    implicit none
    real(dp), intent(in) :: x
    end function
  end interface
  procedure(func) :: f
  s = (b-a) / 6 * (f(a) + 4*f((a+b)/2) + f(b))
end function

end module

使い方

subroutine foo(a, k)
use integrals, only: simpson
real(dp) :: a, k
print *, simpson(f, 0._dp, pi)
print *, simpson(f, 0._dp, 2*pi)

contains

real(dp) function f(x) result(y)
real(dp), intent(in) :: x
y = a*sin(k*x)
end function f

end subroutine foo

type(c_ptr) ポインタの使用#

C では、void * ポインタを使用します。Fortran では、まったく同じ目的で type(c_ptr) を使用できます。

インテグレーター

module integrals
  use types, only: dp
  use iso_c_binding, only: c_ptr
  implicit none
  private
  public simpson

contains

real(dp) function simpson(f, a, b, data) result(s)
  real(dp), intent(in) :: a, b
  interface
    real(dp) function func(x, data)
    use types, only: dp
    implicit none
    real(dp), intent(in) :: x
    type(c_ptr), intent(in) :: data
    end function
  end interface
  procedure(func) :: f
  type(c_ptr), intent(in) :: data
  s = (b-a) / 6 * (f(a, data) + 4*f((a+b)/2, data) + f(b, data))
end function

end module

使い方

module test
  use types, only: dp
  use integrals, only: simpson
  use iso_c_binding, only: c_ptr, c_loc, c_f_pointer
  implicit none
  private
  public foo

  type f_data
    ! Only contains data that we need for our particular callback.
    real(dp) :: a, k
  end type

contains

real(dp) function f(x, data) result(y)
  real(dp), intent(in) :: x
  type(c_ptr), intent(in) :: data
  type(f_data), pointer :: d
  call c_f_pointer(data, d)
  y = d%a * sin(d%k * x)
end function

subroutine foo(a, k)
  real(dp) :: a, k
  type(f_data), target :: data
  data%a = a
  data%k = k
  print *, simpson(f, 0._dp, pi, c_loc(data))
  print *, simpson(f, 0._dp, 2*pi, c_loc(data))
end subroutine

end module

常に、このような再キャストの利点と同様に、Fortran が本当に必要な場合に許可してくれるように、エラーをキャッチするために可能なコンパイル時および実行時のチェックが少なくなるという欠点も伴います。そして、それによって、必然的により多くのリークしやすく、バグが発生しやすいコードになります。したがって、常にコストとメリットのバランスを取る必要があります。

通常、科学プログラミングのコンテキストでは、主な目的は(多数のボタン、ドロップダウン、その他のインターフェイス要素を備えた GUI を作成するのではなく)正確な数式を表現して解決することであり、最も単純で、バグが発生しにくく、最も高速なのは、以前のアプローチのいずれかを使用することです。

transfer() 組み込み関数#

Fortran 2003 より前は、型変換を行う唯一の方法は、transfer 組み込み関数を使用することでした。これは機能的にはメソッド V と同等ですが、より冗長でエラーが発生しやすくなっています。現在では廃止されており、代わりにメソッド V を使用する必要があります。

http://jblevins.org/log/transfer

http://jblevins.org/research/generic-list.pdf

http://www.macresearch.org/advanced_fortran_90_callbacks_with_the_transfer_function

オブジェクト指向アプローチ#

モジュール

module integrals

  use types, only: dp
  implicit none
  private

  public :: integrand, simpson

  ! User extends this type
  type, abstract :: integrand
  contains
    procedure(func), deferred :: eval
  end type

  abstract interface
    function func(this, x) result(fx)
      import :: integrand, dp
      class(integrand) :: this
      real(dp), intent(in) :: x
      real(dp) :: fx
    end function
  end interface

contains

real(dp) function simpson(f, a, b) result(s)
  class(integrand) :: f
  real(dp), intent(in) :: a, b
  s = ((b-a)/6) * (f%eval(a) + 4*f%eval((a+b)/2) + f%eval(b))
end function

end module

抽象型は、積分ルーチンに必要なもの、つまり関数を評価する方法を正確に規定しますが、それ以外はユーザーに何も課しません。ユーザーはこの型を拡張し、eval 型のバインドプロシージャの具体的な実装を提供し、拡張型のコンポーネントとして必要なコンテキストデータを追加します。

使い方

module example_usage

  use types, only: dp
  use integrals, only: integrand, simpson
  implicit none
  private

  public :: foo

  type, extends(integrand) :: my_integrand
    real(dp) :: a, k
  contains
    procedure :: eval => f
  end type

contains

function f(this, x) result(fx)
  class(my_integrand) :: this
  real(dp), intent(in) :: x
  real(dp) :: fx
  fx = this%a*sin(this%k*x)
end function

subroutine foo(a, k)
  real(dp) :: a, k
  type(my_integrand) :: my_f
  my_f%a = a
  my_f%k = k
  print *, simpson(my_f, 0.0_dp, 1.0_dp)
  print *, simpson(my_f, 0.0_dp, 2.0_dp)
end subroutine

end module

void * vs type(c_ptr) と transfer() の完全な例#

ここに、3 つの同等のコードがあります。1 つは void * を使用した C、2 つは type(c_ptr)transfer() を使用した Fortran です。

言語  

方法

リンク

C

void *

https://gist.github.com/1665641

Fortran

type(c_ptr)  

https://gist.github.com/1665626

Fortran

transfer()

https://gist.github.com/1665630

C コードは、コールバックとコンテキストを受け入れる拡張可能なライブラリを作成するための標準的な C アプローチを使用しています。2 つの Fortran コードは、同じことを行う方法を示しています。type(c_ptr) メソッドは C バージョンと同等であり、それが使用されるべきアプローチです。

transfer() メソッドは、完了のためにのみここにあります(Fortran 2003 より前は、それが唯一の方法でした)。ユーザーが各型に対して補助変換関数を作成する必要があるため、少し面倒です。そのため、代わりに type(c_ptr) メソッドを使用する必要があります。