【问题标题】:`Error: Return type mismatch of function` in Fortran 95Fortran 95 中的“错误:函数的返回类型不匹配”
【发布时间】:2014-09-12 15:09:18
【问题描述】:

我决定尝试在 Fortran 95 中实现阶乘函数(f2py 限制),但我的努力只产生了两个返回类型不匹配错误。


解决方案的灵感

在 Haskell 中,我们可以做类似的事情

fac_helper n acc =
  if n <= 1
  then acc 
  else fac_helper (n - 1) (acc * n)

factorial n = fac_helper n 1


尝试的解决方案:fac.f95

recursive function facHelper(n, acc) result(returner)
  integer::n
  integer::acc
  integer::returner
  if (n <= 1) then
    returner = acc
  else
    returner = facHelper(n - 1, n * acc)
  endif
end function facHelper

function factorial(n)
  integer::n
  integer::factorial
  factorial = facHelper(n, 1)
end function factorial


当在 fac.f95 上使用 GNU Fortran (GCC) 4.8.3 作为

gfortran -std=f95 ./fac.f95 -o fac

结果是:

Error: Return type mismatch of function 'fachelper' at (1) (REAL(4)/INTEGER(4))
Error: Return type mismatch of function 'factorial' at (1) (REAL(4)/INTEGER(4))

这些错误(对于不熟悉的 Fortraner 而言)似乎与尝试编译的代码脱节。我确信在尝试的解决方案中没有声明或使用实数。 ...?

尾递归阶乘实现在 Fortran95 中的表现如何?

【问题讨论】:

  • factorial 是如何被调用的?
  • 我没有代表再过几个小时来回答我自己的问题,但问题实际上出在阶乘函数中,不知道 facHelper 是一个整数。一旦获得许可,我将发布我的答案。
  • 您没有发布自己的答案,凯西的答案很好地证明了正确的用法。他甚至提到了隐含的问题。
  • @Vladimir F 您能否为我详细说明一下在哪里放置隐式 none 以便我尝试的解决方案可以按照当前编写的方式工作?如果 casey 真的逐字逐句地使用我尝试的解决方案,那么他唯一会使用implicit none 的地方就是在他的程序测试中。
  • 重点是将您的程序放在模块或主程序中。然后它们彼此具有显式接口。隐含的 none 只是帮助更早地查看代码中的问题。你可以把它放在你的两个函数中。

标签: recursion functional-programming fortran gfortran fortran95


【解决方案1】:

您向我们展示的错误可能与您调用factorial() 的方式有关,而不是在此代码中。如果我将您的代码包装在以下示例中:

program test
  implicit none
  integer :: i
  do i=1,10
    write (*,'(i2,"! = ",i8)') i, factorial(i)
  end do
contains

[cut and paste the code from your question]

end program

并使用 gfortran 4.8.3 编译:

gfortran -std=f95 -o fac fac.f90

我得到了输出:

 1! =        1
 2! =        2
 3! =        6
 4! =       24
 5! =      120
 6! =      720
 7! =     5040
 8! =    40320
 9! =   362880
10! =  3628800

确保您使用正确的参数类型调用factorial(),如果它不在模块或内部过程中,请使用显式接口。我注意到您没有在代码中使用implicit none,因此还要验证您是否明确声明了您正在调用factorial 的变量,以确保使用正确的类型。


如果您将这些过程用作外部过程(例如,不在您的主程序中或包含在模块中),那么为了让调用过程知道会发生什么,您应该使用显式接口。请参阅以下示例。

program test
  implicit none
  integer :: i
  interface 
     function factorial(n)
        integer, intent(in) :: n
        integer :: factorial
     end function factorial
  end interface

 do i=1,10
   write (*,'(i2,"! = ",i8)') i, factorial(i)
 end do
end program

recursive function facHelper(n, acc) result(returner)
  implicit none
  integer, intent(in) :: n, acc
  integer :: returner
  if (n <= 1) then
    returner = acc
  else
    returner = facHelper(n - 1, n * acc)
  endif
end function facHelper

function factorial(n)
  implicit none
  integer, intent(in) :: n
  integer :: factorial
  interface 
     function facHelper(n,acc) 
        integer, intent(in) :: n, acc
        integer :: facHelper
     end function
  end interface
  factorial = facHelper(n, 1)
end function factorial

我对您的示例所做的更改仅限于:

  • 添加了implicit none 以禁止隐式输入
  • 添加了显式接口块

使用implicit none,您最初编译代码的尝试会失败。因为您没有它,所以您对以f 开头的factorial 的外部函数调用被假定为真实的。当您的函数返回一个整数时,这会导致类型不匹配。您通过将factorial 显式声明为整数来解决此问题,但更好的解决方案是完全指定接口,以便编译器可以检查参数而不仅仅是返回类型。在我的代码中,主程序调用factorial,因此添加了显式接口块。同样,factorial 调用 facHelper 并且同样需要一个接口。

您可以通过在模块中或在contains 语句之后的主程序中包含您的函数来避免显式接口块。正如上面的示例应该说明的那样,您提出的算法没有任何更改,更改仅限于 Fortran 如何处理外部过程和隐式类型的问题——在这两种情况下,此示例都选择了显式的最佳实践。

带模块:

我个人会选择将这些函数包含在一个模块中,并完全避免显式接口的需要。看这个例子:

module fact
   private :: facHelper
contains

recursive function facHelper(n, acc) result(returner)
  implicit none
  integer, intent(in) :: n, acc
  integer :: returner
  if (n <= 1) then
    returner = acc
  else
    returner = facHelper(n - 1, n * acc)
  endif
end function facHelper

function factorial(n)
  implicit none
  integer, intent(in) :: n
  integer :: factorial
  factorial = facHelper(n, 1)
end function factorial
end module fact

program test
  use fact
  implicit none
  integer :: i

  do i=1,10
    write (*,'(i2,"! = ",i8)') i, factorial(i)
  end do
end program

在此示例中,factorial 在模块中,facHelper 在模块中,但不能在外部调用(声明为私有并在主程序中隐藏)。您会看到这里的算法与您提出的代码几乎相同,唯一的区别是添加了implicit none。在主程序中,use fact 行让程序知道函数的接口。

【讨论】:

  • 我尝试的解决方案和 Haskell 实现只是为了激发对底线问题的回答:“尾递归阶乘实现在 Fortran95 中的外观如何?”如果你能修改你的答案来解决这个问题,我会更倾向于接受你的答案。
  • 如果上面不清楚,您在问题中提出的解决方案是它的外观(请注意,我将它复制并粘贴到我的示例中而根本没有改变它)。您的问题不在于实现尾递归,而是与您的算法无关的接口规范的通用 Fortran 问题。
  • 这对我来说没有意义。问题中提出的解决方案没有在阶乘函数中声明“integer::facHelper”,在它这样说之前我什至无法编译。
  • @user0xf00 查看我的编辑。如果仍然不清楚,请告诉我。
  • @user0xf00 我还刚刚为您添加了一个带有模块的示例。
【解决方案2】:

问题及解决方案

在对诸如implicit none等问题大惊小怪之后,似乎问题实际上出在阶乘函数中。

即使 facHelper 已被定义为

recursive integer function facHelper(n, acc) result(returner)

并且在 facHelper 之后定义了阶乘函数,阶乘仍然不知道 facHelper 返回一个整数。

然后解决方案是在阶乘函数中添加一行,告诉它 facHelper 是整数:

function factorial(n)
  integer::n
  integer::factorial
  integer::facHelper ! <-- Missing information now available
  factorial = facHelper(n, 1)
end function factorial


回答问题

尾递归阶乘实现在 Fortran95 中的表现如何?

Fortran95 中的尾递归阶乘函数可以实现为:

fac.f95

recursive function facHelper(n, acc) result(returner)
  integer::n
  integer::acc
  integer::returner
  if (n <= 1) then
    returner = acc
  else
    returner = facHelper(n - 1, n * acc)
  endif
end function facHelper

function factorial(n)
  integer::n
  integer::factorial
  integer::facHelper
  factorial = facHelper(n, 1)
end function factorial

或者,更美观(在我看来):

fac.f95

recursive integer function facHelper(n, acc) result(returner)
  integer::n
  integer::acc
  if (n <= 1) then
    returner = acc
  else
    returner = facHelper(n - 1, n * acc)
  endif
end function facHelper

integer function factorial(n)
  integer::n
  integer::facHelper
  factorial = facHelper(n, 1)
end function factorial

这两个现在都可以在 GNU Fortran (GCC) 4.8.3 下编译

gfortran --std=f95 -c ./fac.f95


多余的复活节彩蛋

将 f2py v2 与 NumPy v1.8.0 结合使用

虽然 fac.f95 abover 的两个版本都可以使用 gfortran 编译,但第二个版本会导致 f2py 认为来自 facHelper 的返回者是真实的。但是,f2py 确实可以正确处理 fac.f95 的第一个版本。

我想在 Fortran 中对(时间)尾递归阶乘进行基准测试。我添加了阶乘的非尾递归版本(名为 vanillaFac)。整数大小也增加到 kind=8。

fac.f95 现在包含

recursive function tailFacHelper(n, acc) result(returner)
  integer (kind=8)::n
  integer (kind=8)::acc
  integer (kind=8)::returner
  if (n <= 1) then
    returner = acc
  else
    returner = tailFacHelper(n - 1, n * acc)
  endif
end function tailFacHelper

function tailFac(n)
  integer (kind=8)::n
  integer (kind=8)::tailFac
  integer (kind=8)::tailFacHelper
  tailFac = tailFacHelper(n, 1_8)
end function tailFac

recursive function vanillaFac(n) result(returner)
  integer (kind=8)::n
  integer (kind=8)::returner
  if (n <= 1) then
    returner = 1
  else
    returner = n * vanillaFac(n - 1)
  endif
end function vanillaFac

新的fac.f95是用

编译的
f2py --overwrite-signature --no-lower fac.f95 -m liboptfac -h fac.pyf;
f2py -c --f90flags=--std=f95 --opt=-O3 fac.pyf fac.f95;
f2py --overwrite-signature --no-lower fac.f95 -m libnooptfac -h fac.pyf;
f2py -c --f90flags=--std=f95 --noopt fac.pyf fac.f95;

我写了一个python脚本timer.py

import liboptfac
import libnooptfac
import timeit
def py_vanilla_fac(n):
  if n <= 1:
    return 1
  else:
    return n * py_vanilla_fac(n - 1)
def py_tail_fac_helper(n, acc):
  if n <= 1:
    return acc
  else:
    return py_tail_fac_helper(n - 1, n * acc)
def py_tail_fac(n):
  return py_tail_fac_helper(n, 1)

LOOPS = 10 ** 6
print "\n*****Fortran (optimizations level 03 enabled)*****"
print "\nliboptfac.vanillaFac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: liboptfac.vanillaFac(20), repeat = 10, number = LOOPS))
print "\nliboptfac.tailFac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: liboptfac.tailFac(20), repeat = 10, number = LOOPS))
print "\nliboptfac.tailFacHelper(20, 1)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: liboptfac.tailFacHelper(20, 1), repeat = 10, number = LOOPS))
print "\n\n*****Fortran (no optimizations enabled)*****"
print "\nlibnooptfac.vanillaFac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: libnooptfac.vanillaFac(20), repeat = 10, number = LOOPS))
print "\nlibnooptfac.tailFac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: libnooptfac.tailFac(20), repeat = 10, number = LOOPS))
print "\nlibnooptfac.tailFacHelper(20, 1)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: libnooptfac.tailFacHelper(20, 1), repeat = 10, number = LOOPS))
print "\n\n*****Python*****"
print "\npy_vanilla_fac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: py_vanilla_fac(20), repeat = 10, number = LOOPS))
print "\npy_tail_fac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: py_tail_fac(20), repeat = 10, number = LOOPS))
print "\npy_tail_fac_helper(20, 1)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: py_tail_fac_helper(20, 1), repeat = 10, number = LOOPS))
print "\n\n\n"

终于来了

python timer.py

输出:

*****Fortran (optimizations level 03 enabled)*****

liboptfac.vanillaFac(20)
1000000 calls
Best of ten:  0.813575983047

liboptfac.tailFac(20)
1000000 calls
Best of ten:  0.843787193298

liboptfac.tailFacHelper(20, 1)
1000000 calls
Best of ten:  0.858899831772


*****Fortran (no optimizations enabled)*****

libnooptfac.vanillaFac(20)
1000000 calls
Best of ten:  1.00723600388

libnooptfac.tailFac(20)
1000000 calls
Best of ten:  0.975327014923

libnooptfac.tailFacHelper(20, 1)
1000000 calls
Best of ten:  0.982407093048


*****Python*****

py_vanilla_fac(20)
1000000 calls
Best of ten:  6.47849297523

py_tail_fac(20)
1000000 calls
Best of ten:  6.93045401573

py_tail_fac_helper(20, 1)
1000000 calls
Best of ten:  6.81205391884

【讨论】:

  • 仅将facHelper 声明为整数是处理外部过程的一种相当古老的风格。声明显式接口或将过程放在模块中更有意义。显式接口的好处是编译器可以检查参数类型,而不仅仅是返回类型。
  • 这是一个非常不幸的修复方法。当您拥有更高级的参数类型并且它不提供任何错误检查时,它将失败。使用模块或使它们内部化要好得多。对于 f2py,模块方法是他要走的路。
  • @Vladmir 错误检查对于演示实现阶乘的请求是不合理的。 Haskell 示例不执行错误检查,并且对于问题的意图和目的完全有效。 Wolfram Mathworld 也不执行任何错误检查。
  • @casey 通过将 facHelper 声明为整数,您的意思是“递归整数函数 facHelper”,还是来自阶乘内部的“integer::facHelper”?如果您可以根据“尾递归阶乘实现在 Fortran95 中的外观如何?”的底线问题修改您的答案以包含一个完整的示例,我会更倾向于接受您的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多