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

void *

https://gist.github.com/1665641

Fortran

type(c_ptr)  

https://gist.github.com/1665626

Fortran

transfer()

https://gist.github.com/1665630

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.