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 |
|
|
Fortran |
|
|
Fortran |
|
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.