# 回调函数中的类型转换
在回调函数的使用中，经常需要传递一些参数给被调用函数，例如`f(x)=a*sin(k*x)`，这时就需要类型转换。
类型转换基本上有五种不同的方法，每种方法都有自己的优缺点。

方法I、II和V可用于C和Fortran。方法III和方法IV只能在Fortran中使用。方法VI已过时，不应使用。
## 工作数组
传递一个“工作数组”，该数组包含调用方所需的所有内容，并由被调用的例程解压缩。这是一种古老的方式--例如，LAPACK正是使用这种方法。

积分器:

``` fortran
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
```

用法:

``` fortran
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
```

## 通用结构

定义一个通用的结构，包含目前实际的需求（甚至在未来的需求）。
这个通用结构类型可以根据未来的需求或设想而改变，但不太可能需要比如从传递实数更改为文本编辑器的实例化。

积分器:

``` fortran
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
```

用法:

``` fortran
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
```

真正需要的灵活性只有这么多。例如，可以为此定义两种结构类型，一种用于情况A，另一种用于情况B。然后，每一个都将具有足够的通用性，并包含所有需要的部分和正确的标志。

注意：它不必是“一个包罗万象的抽象类”或空类。在“全部”和“无”之间有自然和可行的选择。

## 私有的模块变量

通过传入模块变量来完全隐藏变量参数。

积分器:

``` fortran
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
```

用法:

``` fortran
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
```

但是，如果可能的话，最好避免这样的全局变量--即使实际上只是半全局变量(semi-global)。虽然有时这可能是最简单最干净的方式，然而，只要沿着II或IV的路线稍加思考，通常会有一个更好、更安全、更明确的方案。

## 嵌套函数

积分器:

``` fortran
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
```

用法:

``` fortran
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)`来达到完全相同的目的。

积分器:

``` fortran
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
```

用法:

``` fortran
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允许您在真正需要的情况下使用这种重新转换的优点，
但同时也有一些缺点，即编译和运行时检查可能无法捕获错误。因此，这将不可避免地会有更多的漏洞百出、容易出现bug的代码，所以需要考虑平衡成本和收益。

通常，在科学计算中，主要目的是表示和求解精确的数学公式（而不是创建一个包含无数按钮、下拉框和其他界面元素的GUI)，所以，最简单、最不容易出错、最快的选择是使用前面的方法之一。

## `transfer`内置函数

在Fortran 2003之前，做类型转换的唯一方法是使用`transfer`内置函数。

它在功能上与方法V等效，但更冗长，更容易出错。它现在已经过时了，应该使用方法V来代替。

Examples:

<http://jblevins.org/log/transfer>

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

<http://www.macresearch.org/advanced_fortran_90_callbacks_with_the_transfer_function>

## 面向对象的策略

如下模块
``` fortran
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 类型绑定过程的具体实现，并添加必要的上下文数据作为扩展类型的成员。

用法:

``` fortran
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
```

## 关于 C的`void *`，Fortran 的两种实现方式 `type(c_ptr)`和`transfer`的完整示例

这里有三个等价代码：在C中使用`void*`，在Fortran中使用`type(c_ptr)`和`transfer()`：


| **Language**    | **Method**           | **Link**                          |
|:-               |:-                    |:-                                 |
| 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()`方法只是为了完善对比（在Fortran2003之前，这是唯一的方法），它有点麻烦，因为用户需要为每个类型创建辅助转换函数。因此，推荐使用`type(c_ptr)`方法代替它。
