Recently, I have been trying to port or interface some Fortran code for a project in Python. It was quite an uphill struggle for me. I thought I knew how to write subroutines in Fortran 90, but when I decided to write pure functions in the latest Fortran style, which was Fortran 2008, it did not go as well as I expected. The compiler yelled at me a screenful of errors. After quite a while of debugging a simple array function and reading a bunch of StackOverflow questions, I learned how to deal with these pitfalls the hard way.
-
“Function result … at (1) has no implicit type”
The example below will raises an error of this at the compile-time.
function foo(x) result(bar) implicit none integer, intent(in) :: x bar = 42 end function foo
This is a common compiler error thrown at functions. What it means is that the compiler cannot determine the return type of the function; it must be explicitly declared. But where and how to declare the type? There are two ways to fix it. One way is to declare the type of function at the beginning,
integer function foo(x) result(bar) implicit none integer, intent(in) :: x bar = 42 end function foo
This seems to be the recommended way, because it makes it clear that this function returns an integer (kinda like how you would write a C function.)
Another way is to declare the result variable inside the function body. This works just fine. But nowadays it seems that the former one is the more prevalent style.
function foo(x) result(bar) implicit none integer, intent(in) :: x integer :: bar bar = 42 end function foo
Note that you cannot initialize the result variable
bar
in the example above. For example, if you writeinteger :: bar = 42
, the compiler will again yell at you “Function result ‘bar’ at (1) cannot have an initializer”. I think this style would be more prone to error thaninteger function foo(x) result(bar)
.So, in short, always declare the return type if the function does not return an array. (I’ll deal with the array function case later.)
-
Function result is defined in the function header, not in the function body. You cannot decide which variable to return later in the function body, because there is no C-like
return
statement (or, you could also say, the Fortranreturn
statement has an entirely different meaning).The result variable is defined with the
result
keyword. For example, ininteger function foo(x) result(bar) ... end function foo
the variable to return to the caller is
bar
and cannot be anything else.The
result
statement can be omitted. In this case, the result variable will have the same name as the function. For example, ininteger function foo(x) ... end function foo
the result variable is
foo
.On the other hand, the absence of a C-like
return
statement makes perfect sense because the function already knows which variable is the result variable the moment it is declared. -
Do not declare the type of the result variable twice. If the result variable type is already declared in the function header, there is no need to declare it again in the function body. This means the following example is wrong.
integer function foo(x) result(bar) integer, intent(in) :: x integer :: bar = 42 end function foo
The compiler would raise an error for duplicate declarations, “Symbol ‘bar’ at (1) already has basic type of INTEGER”.
-
Do not use
intent(out)
in the declaration of the result variable in the function body. The following example is wrong.function foo(x) result(bar) implicit none integer, intent(in) :: x integer, intent(out) :: bar bar = 42 end function foo
The compiler will raise an error at the line
function foo(x) result(bar)
: “Symbol at (1) is not a DUMMY variable”. It is not obvious that this message would refer to the misuse ofintent(out)
. -
Functions that return an array
When the result variable is an array, we cannot declare its type in the function header. This means the following would not work.
integer(1:5) function vec_foo(x) result(bar) ... end function vec_foo
In this case, we do need to declare the result variable in the body of the function. For example,
! an array function that returns a fixed-length array function foo(x) result(bar) implicit none integer, intent(in) :: x integer, dimension(5) :: bar bar = [1, 2, 3, 4, 5] end function foo
A function may also return a automatically allocated array whose size depends on the arguments. The result variable just needs to be declared with dimension variables determined from the arguments.
See also: https://stackoverflow.com/questions/43768605/function-result-has-no-implicit-type.
-
Array dimension must be a constant or an expression that evaluates to a constant in the declaration of an automatic array. (It is the same in C99.) The compiler error corresponding to this would be “Unexpected data declaration statement at (1)”. See also: https://stackoverflow.com/questions/13630254/unexpected-data-declaration-error-in-fortran-when-creating-array.
-
There are two ways for a program to use a function defined in the same source file. The function may be included in the program,
program my_program ... print *, foo(x) contains integer function foo(x) ... end function foo end program my_program
Or, the function can be defined in a module that is placed in the same source file as the main program. (This seems to be the preferred style.)
module my_module contains integer function foo(x) ... end function foo end my_module program my_program use my_module implicit none ... print *, foo(x) end program my_program
If something goes wrong, the
gfortran
compiler will raise an error “Function result has no implicit type”, which is not very helpful in my opinion.See also: https://stackoverflow.com/questions/17751863/function-has-no-implicit-type.
For more on the use of modules, subroutines, and functions: https://stackoverflow.com/questions/8412834/correct-use-of-modules-subroutines-and-functions-in-fortran.
-
Polymorphism is only allowed by
elemental
functions, which apply the same operations on every element of an array. This means that function overloading can work onreal
andreal, dimension(10)
, but cannot work onreal
andcharacter, dimension(80)
(a string of 80 characters long). For example, there is no easy way to mimic the Julia behavior of multiple dispatchadd(x::Number, y::Number) = x + y add(x::String, y::String) = string(x, y)
It also means that a function cannot decide to return a scalar or vector based on the result of evaluating a condition within the function at runtime. For example, in Python, we can write
import numpy as np def scalar_or_vector(flag: bool) -> Union[int, np.ndarray]: if flag: return 0 else: return np.ones(10)
Again, there is no way this can be easily reproduced in Fortran.
With some effort, however, there is a workaround using the
interface
construct. See the example presented in: https://stackoverflow.com/questions/49014237/fortran-function-that-returns-scalar-or-array-depending-on-input. -
More about functions: Use the
pure
keyword for pure functions, i.e., functions that have no side effects. Use theelemental
keyword for elementwise operations that are to be applied on an array. I like to use them because these restrictions will help you write the program in a defensible way.