Conversão de Tipos em Callbacks#
Há essencialmente cinco jeitos diferentes de fazer a conversão de tipo, cada qual com suas vantagens e desvantagens.
Os métodos, I, II e V são usados em C e Fortran. Os métodos III e IV estão disponíveis somente em Fortran. O método VI é obsoleto e não deve ser usado.
Arrays de Trabalho#
Passar uma «matriz de trabalho» a qual é empacotada com tudo que é necessária pelo invocador e desempacotado pela rotina invocada. Esse é o modo antigo – p.ex.:, como o LAPACK faz isso.
Cálculo de Integrais:
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
Uso:
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
Estrutura Geral#
Define uma estrutura geral que engloba as variações que você de fato precisa (ou que irá precisar futuramente). Esse tipo de estrutura único pode então ser mudado caso necessário se futuras ideias/necessidades permitirem mas que não precise passar, digamos, números reais para, por exemplo, uma instanciação de um editor de texto.
Cálculo de Integrais:
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
Uso:
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
Há apenas flexibilidade o suficiente à necessidade. Por exemplo, você pode definir dois tipos de estrutura para esse propósito, uma para Schroedinger e uma para Dirac. Cada qual seria suficientemente geral e conterá todas as peças necessárias com todos os rótulos corretos.
O ponto é: não é necessário ser «um tipo abstrato que engloba tudo» ou tal. Há opções naturais e viáveis entre «tudo» e «nada».
Variáveis Privadas do Módulo#
Esconde completamente os argumentos das variáveis ao passar variáveis de módulos.
Cálculo de Integrais:
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
Uso:
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
Contudo é melhor evitar variáveis globais – embora seja apenas semi-global – se possível. Mas algumas vezes é a forma mais simples e limpa. Contudo, com um pouco de avaliação, há geralmente uma forma melhor, mais seguro e mais explícita ao longo das linhas II ou IV.
Funções Aninhadas#
Cálculo de Integrais:
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
Uso:
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
Usando Ponteiro type(c_ptr)#
Em C, usa-se o ponteiro void*
. Em Fortran, pode-se usar type(c_ptr)
para o mesmo propósito.
Cálculo de Integrais:
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
Uso:
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
Como sempre, com as vantagens de tal reconversão, como Fortran te permite fazer o que você quer fazer, vem as desvantagens que os checadores em tempo de compilação e execução conseguem capturar erros; e com isso, código com mais bugs. Então busque balancear os custos e os benefícios.
Geralmente, no contexto de programação científica, onde o foco principal é representar e solucionar formulações matemáticas (em oposição à criar GUIs com vários butões, drop-downs, e outros elementos de interface), mais simples, menos suscetível a erros, e mais rápido é usar uma das abordagens anteriores.
Função Intrínseca transfer()#
Antes do Fortran 2003, a única forma de realizar conversão de tipos era usando a função intrínseca transfer
. É funcionalmente equivalente ao método V, mas mais verboso e mais suscetível à erros. Esse método é agora obsoleto e deve-se usar o método V.
Exemplos:
http://jblevins.org/log/transfer
http://jblevins.org/research/generic-list.pdf
http://www.macresearch.org/advanced_fortran_90_callbacks_with_the_transfer_function
Abordagem Orientada à Objetos#
O módulo:
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
O tipo abstrato prescreve exatamente o que a rotina de integração necessita, isto é, um método que avalie a função, mas imponha mais nada ao usuário. O usuário estende esse tipo, provendo uma implementação concreta do procedimento vinculado ao tipo de avaliação e adicionando dados de contexto necessário como componentes do tipo estendido.
Uso:
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
Exemplo completo do void * vs type(c_ptr) e transfer()#
Aqui há três códigos equivalente: um em C usando void*
e dois códigos em Fortran usando type(c_ptr)
e transfer()
:
Linguagem |
Método |
Link |
---|---|---|
C |
|
|
Fortran |
|
|
Fortran |
|
O código em C usa a abordagem do padrão C para escrever bibliotecas extensíveis que usam callbacks e contextos. O dois códigos Fortran mostram como fazer o mesmo. O método type(c_ptr)
é equivalente à versão C e essa é a abordagem que deve ser usada.
O método transfer()
está aqui apenas para complementação (antes do Fortran 2003, esse era o único jeito) e é um pouco complexo, porque o usuário precisa criar funções de conversão auxiliares para cada tipo. Assim sendo, o método type(c_ptr)
deve ser usado.