Приведение типа в функциях обратного вызова#

Существует пять различных способов приведения типа, каждый из которых имеет свои преимущества и недостатки.

Методы 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

На самом деле требуется лишь только такая гибкость. Например, вы можете определить два типа структур для этой цели, один для уравнения Шредингера и один для уравнения Дирака. Каждый из них будет достаточно общим и будет содержать все необходимые части с правильными обозначениями.

Суть в том, что это не обязательно должен быть «один абстрактный тип, охватывающий всё». Существуют естественные и жизнеспособные варианты между «всё» и «ничего».

Переменные частного модуля#

Полностью скройте аргументы-переменные, передавая переменные модуля.

Реализация:

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 позволяет осуществить, если вы действительно этого хотите, связаны и недостатки: возможно выполнить меньше проверок времени компиляции и времени выполнения для выявления ошибок; вместе с этим появляется неизбежно больше возможных утечек, подверженный ошибкам код. Поэтому всегда нужно соизмерять потери и выгоды.

Обычно, в контексте научного программирования, где основным направлением является представление и решение точных математических формулировок (в отличие от создания графического интерфейса с бесчисленным количеством кнопок, выпадающих списков и других элементов интерфейса), наиболее простым, наименее подверженным ошибкам и быстрым является использование одного из предыдущих подходов.

Встроенная функция 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()#

Здесь приводятся три эквивалентных метода: один на языке C с использованием void * и два на языке Fortran с использованием type(c_ptr) и функции transfer():

Язык  

Метод

Ссылка

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 для написания расширяемых библиотек, которые принимают в качестве аргумента функции обратного вызова и контексты. Два метода на языке Fortran показывают, как сделать то же самое. Метод type(c_ptr) эквивалентен версии на языке C, и именно этот подход следует использовать.

Метод с функцией transfer() приведён здесь только для полноты картины (до стандарта Fortran 2003 он являлся единственным), и он немного громоздок, поскольку пользователю необходимо создавать вспомогательные функции преобразования для каждого из своих типов. Вместо него следует использовать type(c_ptr).