简体   繁体   中英

`Error: Return type mismatch of function` in Fortran 95

I decided to try implementing the factorial function in Fortran 95 (f2py limitation) but my efforts are only yielding two return-type-mismatch errors.


Inspiration for solution

In Haskell, we can do something like

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

factorial n = fac_helper n 1


Attempted solution: 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


When GNU Fortran (GCC) 4.8.3 is used on fac.f95 as

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

the result is:

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))

These errors appear (to the un-familiar Fortraner) as out of touch with the code of which compilation was attempted on. I am certain that there are no real numbers declared or used in the attempted solution. ...?

How would the tail-recursive factorial implementation look in Fortran95?

The error you are showing us is likely related to how you are calling factorial() and not in this code. If I wrap your code in the following example:

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

and compile with gfortran 4.8.3:

gfortran -std=f95 -o fac fac.f90

I get the output:

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

Make sure you are calling factorial() with the proper argument types and if it is not in a module or internal procedure, use an explicit interface. I notice that you are not using implicit none in your code, so also verify that you are explicitly declaring the variables you are calling factorial with to ensure the proper type is used.


If you are using these procedures as external procedures (eg not in your main program or contained in a module) then to let the calling procedure to know what to expect, you should use an explicit interface. See the following example.

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

The changes I've made to your example are limited to:

  • Added implicit none to disallow implicit typing
  • Added explicit interface blocks

With implicit none your original attempt at compiling the code would have failed. Because you did not have it, your external function call to factorial , which starts with f is assumed to be real. When your function returns an integer, this causes a type mismatch. You solved this by explicitly declaring factorial to be an integer, but a better solution is to completely specify the interface so the compiler can check the arguments and not just the return types. In my code the main program calls factorial and so that is where an explicit interface block has been added. Likewise, factorial calls facHelper and similarly needs an interface for it.

You can avoid explicit interface blocks by containing your functions within a module or in the main program after a contains statement. As the above example should illustrate, nothing has been changed in your proposed algorithm, the changes are limited to issues with how Fortran handles external procedures and implicit typing -- in both cases this example has chosen the best practice of being explicit.

With modules:

I would personally choose to contain these functions in a module and entirely avoid the need for explicit interfaces. See this example:

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

In this example, factorial is in a module and facHelper is in the module but not callable externally (declared private and hidden from the main program). You'll see that your algorithm here is almost identical to your proposed code, the only difference being the implicit none that is added. Here in the main program the line use fact lets the program know about the interface to the function.

Problem and Solution

After fussing around with things like implicit none etc, it appears that the problem was actually in the factorial function.

Even if facHelper had been defined with

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

and having the factorial function being defined after facHelper, factorial still would not know that facHelper returns an integer.

The solution then is to add a line to the factorial function telling it that facHelper is integer:

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


Answering the question

How would the tail-recursive factorial implementation look in Fortran95?

The tail-recursive factorial function in Fortran95 could be implemented as:

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

Or, more asthetically pleasing (in my opinion):

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

Both of these will now compile under GNU Fortran (GCC) 4.8.3 with

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


Superfluous Easter Egg

Using f2py v2 with NumPy v1.8.0

While both versions of fac.f95 abover will compile with gfortran, the second version will cause f2py to think that the returner from facHelper is a real. However, f2py does correctly handle the first version of fac.f95.

I wanted to benchmark (for time) the tail-recursive factorial in Fortran. I added a non-tail-recursive version of the factorial (named vanillaFac). The integer sizes were also increased to kind=8.

fac.f95 now contains

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

The new fac.f95 was compiled with

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;

I wrote a python script 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"

Finaly, calling

python timer.py

outputs:

*****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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM