Rzutowanie typów w wywołaniach zwrotnych#

Istnieje zasadniczo pięć różnych sposobów na przeprowadzenie rzutowania typów, z których każdy ma swoje plusy i minusy.

Metody I, II i V mogą być używane zarówno w języku C, jak i Fortranie. Metody III i IV dostępne są tylko w Fortranie. Metoda VI przestarzała i nie powinna być używana.

Tablice robocze#

Podaj „tablicę roboczą”, która jest spakowana ze wszystkim, czego potrzebuje funkcja wywołująca i rozpakowana przez wywołaną procedurę. Jest to stary sposób - np. tak jak robi to LAPACK.

Integrator:

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

Użycie:

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

Ogólna struktura#

Zdefiniuj strukturę generalną, która zawiera wariację, których faktycznie potrzebujesz (lub, których będziesz potrzebował w przyszłości). Ten pojedynczy typ struktury może się później zmienić jeśli będzie to koniecznie i gdy pozwolą na to przyszłe potrzeby/pomysły, jednak prawdopodobnie nie trzeba będzie zmieniać np. przekazywania liczb rzeczywistych i instancji edytora tekstu.

Integrator:

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

Użycie:

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

Potrzebna jest tylko elastyczność. Na przykład, możesz zdefiniować w tym celu dwa typy struktur, jeden dla Schroedingera i jeden dla Diraca. Każdy z nich byłby wtedy wystarczająco ogólny i zawierałby wszystkie potrzebne elementy ze właściwymi etykietami.

Chodzi o to, że nie musi to być „jeden abstrakcyjny typ obejmujący wszystko” lub totalna porażka. Istnieją naturalne i wykonalne opcje między „wszystkim” a „niczym”.

Prywatne zmienne modułu#

Ukryj całkowicie zmienne argumenty poprzez przekazanie zmiennych modułu.

Integrator:

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

Użycie:

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

Najlepiej jest jednak unikać takich zmiennych globalnych (nawet jeśli są tylko pół-globalne) jeśli to możliwe. Czasami jednak może to być najbardziej przejrzysty i najprostszy sposób. Często z odrobiną namysłu istnieje lepsza, bezpieczniejsza i bardziej bezpośrednia droga dzięki sposobom II lub IV.

Funkcje zagnieżdżone#

Integrator:

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

Użycie:

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

Używanie wskaźnika typu (c_ptr)#

W języku C można użyć wskaźnika typu void*. W Fortranie type(c_ptr)spełnia taką samą funkcję.

Integrator:

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

Użycie:

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

Jak zawsze, z zaletami ponownego rzutowania na jakie pozwala Fortran pojawiają się również wady, takie jak zmniejszenie liczby sprawdzeń w czasie kompilacji i wykonania programu w celu wyłapania błędów, a wraz z tym otrzymujemy bardziej nieszczelny i skłonny do błędów kod. Tak więc zawsze trzeba zrównoważyć koszty i benefity.

Zazwyczaj, w kontekście programu naukowego, gdzie główny nacisk kładzie się na przedstawienie i rozwiązanie dokładnej formuły matematycznej (w przeciwieństwie do tworzenia interfejsu graficznego z niewyobrażalną ilością przycisków, rozwijanych list i innych elementów), najprostsze, najmniej skłonne do błędów i najszybsze jest zastosowanie któregoś z poprzednio wymienionych sposobów.

transfer() Funkcja wewnętrzna#

Przed Fortranem 2003 jedynym sposobem na rzutowanie typów było użycie funkcji wewnętrznej transfer. Spełnia ona taki sam cel jak metoda V, ale jest bardziej rozwlekła i skłonna do błędów. Jest już przestarzała i zamiast niej powinno się stosować metodę V.

Przykłady:

http://jblevins.org/log/transfer

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

http://www.macresearch.org/advanced_fortran_90_callbacks_with_the_transfer_function

Podejście obiektowe#

Moduł:

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

Typ abstrakcyjny określa dokładnie czego potrzebuje rutyna integracyjna, czyli metodę określenia wartości funkcji, ale nie narzuca użytkownikowi niczego więcej. Użytkownik rozszerza ten typ poprzez zapewnienie konkretnej implementacji procedur powiązanych typu eval oraz dodanie niezbędnych danych kontekstowych jako komponenty typu rozszerzonego.

Użycie:

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

Przykład porównania void * vs type(c_ptr) i transfer()#

Tutaj są trzy równowartościowe przykłady kodu: jeden w C przy użyciu void* oraz dwa przykłady w Fortranie, jeden przy użyciu type(c_ptr), a drugi transfer():

Język  

Metoda

Link

C

void *

https://gist.github.com/1665641

Fortran

type(c_ptr)  

https://gist.github.com/1665626

Fortran

transfer()

https://gist.github.com/1665630

Kod w C używa standardowego podejścia w C dla pisania rozszerzalnych bibliotek, które akceptują wywołania zwrotne i konteksty. Dwa przykłady kodu Fortran pokazują jak zrobić to samo. Metoda type(c_ptr) jest równoważna wersji C i jest to podejście, które powinno być używane.

Metoda transfer() jest tutaj tylko dla kompletności poradnika (przed Fortran 2003 był to jedyny sposób) i jest trochę nieporęczna, ponieważ użytkownik musi stworzyć funkcje posiłkowe dla konwersji dla każdego z jego typów. Dlatego, metoda type(c_ptr) powinna być używana zamiast niej.