Function and subroutine
February 2020
Back to index.html.
Function and subroutine
Program units
In the previous chapters, the Fortran program was in a single unit defined in the program
statement. It is called main program; in the previous chapter, any program you wrote had the main program. When the main program gets longer, it is a good idea to break the program down into small, manageable pieces. Such a small group of code is called program unit.
A typical program-unit is a function. We have looked at several intrinsic functions. In Fortran, you can define a custom function as a block of programs. A function accepts arguments (input values), and it always returns a single value.
Another program-unit is subroutine, which is more flexible than functions. A subroutine can accept any number of values as the argument, and it can return any number of values (even no return values). Functions and subroutines are similar, but the only difference is whether it returns a single value or not. In the later sections in this tutorial, we refer to functions and subroutines as procedures.
External function and subroutines
We first look at how external procedures (functions or subroutines) are defined. An external procedure is an independent program unit. The main program does not know what the external is, so the programmer should tell the main program what they are.
External function
Definition
As seen in the previous chapters, a function has a form like sqrt(x)
with a name and arguments, and it returns a value. A custom function is defined with the name, what kind of arguments it accepts, and what it returns. The function
statement defines a new function. Here is a template of the definition.
function name(arguments) result(output)
List of arguments
Definition of output
...
end function name
For example, the following function returns the cubic root of the argument. It works because \(v=\sqrt[3]{x}=x^{\frac{1}{3}}\).
function cubicroot(x) result(v)
implicit none
real,intent(in) :: x
real :: v
v = x**(1.0/3.0)
end function cubicroot
The usage of custom function is the same as the intrinsic functions. It can take variables or literals as arguments.
real :: my_value,output
! case 1
my_value = 1.5
output = cubicroot(my_value)
! case 2
print *,cubicroot(2.0)
! case 3
my_value = cubicroot(my_value)/1.25
You can see how a function is defined.
- A function is defined between
function
andend function
. - The input values are passed through the argument variables.
- The
function
statement defines its name, the argument variables in()
, and the return variable inresult()
. - You should put
implicit none
inside the function. - At the top of the function, all arguments should be declared as variables with the status like
intent(in)
. - Also, the return variable should be declared. The value held by this variable will be returned when the function finishes.
When defining the function, you know the type of input, but you do not know the actual value of the input. So, you should declare the input as an argument variable with an arbitrary name, and use it in the function. The argument variable is a “reference” (or alias) to the original value, and accessing the argument variable is equivalent to access to the original variable or literals. Because the argument variable is just a label to the original data, some people call it dummy variable or dummy argument. In the above example, you can think x
refers to the original variable (or literal).
The function
statement defines an independent program-unit from the main program. As you do in program
, you have to put implicit none
in the function. All the variables declared inside the function are unrelated to the main program.
The intent()
attribute declares the status of the input variable to clarify which input can be changed (or unchanged).
intent(in)
: It will be used as an input variable, and it will not be altered in the function.intent(out)
: It will not be used as an input variable, but it will be altered in the function.intent(inout)
: It may be used as an input variable, and also, it may be altered in the function.
If the argument does not have any above attributes, it will be treated as intent(inout)
. Note that if the argument has intent(out)
, you must put a value to this argument. Otherwise, the program may return a nonsense value.
Technically, you can define a function that returns multiple values with argument variables with intent(out)
. However, in modern Fortran, there is a recommendation that the function does not change the input arguments, and it just return a value. Changing the input arguments, you should use subroutine
which is intended for such purposes.
Alternative definition
One may like an alternative declaration of a function.
real function cubicroot(x)
implicit none
real,intent(in) :: x
cubicroot = x**(1.0/3.0)
end function cubicroot
In this way, the return type is specified in front of the function name. The name of the return variable is the same as the function name, and you do not need the result
keyword and the declaration of the variable. I am not going to use this style in the rest of the tutorial.
Program structure
When using this custom function in the main program, you can put the function in the same source file, for the simplest usage. Then, using the interface
statement, you tell the main program what the custom function you are going to use is.
program custom
implicit none
real :: a,b
interface
function cubicroot(x) result(v)
implicit none
real,intent(in) :: x
real :: v
end function cubicroot
end interface
! use a custom function as a regular function
read *,a
b = cubicroot(a)
print *,b
end program custom
function cubicroot(x) result(v)
implicit none
real,intent(in) :: x
real :: v
v = x**(1.0/3.0)
end function cubicroot
The interface
statement includes the definition of a custom function, including the name, the argument variables, and the return value. It is precisely the same as the first a couple of lines in the function
statement. If you use several external procedures, you can put all of their definitions in the interface
statement.
You may think it is awkward that you have to repeat the definition of a function in interface
in the main program. It is frustrating, but it is required because the main program does not know what cubicroot
is. The external function is unrelated to the main program, which does not know the type of arguments and its return value. If the main program fails to use the custom function, the program may crash or result in wrong behavior. This redundancy in interface
can be removed by using a module that we introduce in the next chapter.
Scope of variable
Some readers should wonder what happens if the main program and the external function both have the same variable names. Are these variables share the values between two program units? Or, the same variable name does not mean to share the actual value? Another question is the accessibility to the variables. Can a program unit refer to a variable defined in a separate program unit? All the answers is “no”.
The external function is independent of the main program. The function should be self-descriptive, and all the variables in this function are unrelated to ones used outside the function. A variable is accessible only in the program unit where the variable is declared. Scope is the region of a program unit where the variable is valid.
When a variable is accessive from a single program-unit, we say it is a local (or private) variable. All the variables declared in an external function are local. The scope of any variables defined in the function is limited to the inside of the function. See the following example to see the scope of variables.
program custom
implicit none
real :: a,b
...
! This a is unrelated to the function's a.
read *,a
b = cubicroot(a)
print *,b
end program custom
function cubicroot(x) result(v)
implicit none
real,intent(in) :: x
real :: v,a
! This a is a local variable exclusively used in this function.
a = (1.0/3.0)
v = x**a
end function cubicroot
In this example, the variable a
is defined both in the main program and the function, but two are unrelated. Using the scope rule, you don’t have to worry about the conflict of the names among program units.
External subroutine
Definition
The definition of an external subroutine is the same as function, but using a different statement subroutine
.
subroutine name(arguments)
List of arguments
...
end subroutine name
First of all, you do not need a return value for the subroutine. You do not have to return any values. If you want to return some values, you have to alter the arguments. You can change multiple arguments with intent(out)
and intent(inout)
.
As an example, the following subroutine returns two solutions of a quadratic equation, \(ax^2+bx+c=0\).
subroutine solutions_quadratic_equations(a,b,c,s1,s2)
implicit none
real,intent(in) :: a,b,c
real,intent(out) :: s1,s2
real :: d
d = b**2 - 4*a*c
if(d>=0) then
s1 = (-b + sqrt(d))/(2*a)
s2 = (-b - sqrt(d))/(2*a)
else
print *,'b**2 - 4*a*c < 0'
stop
end if
end subroutine solutions_quadratic_equations
In this subroutine, the first 3 arguments (a
,b
, and c
) should be for input, and the last two (s1
and s2
) are for output. The solutions are not defined as real numbers if \(d=b^2-4ac<0\), and in this case, the program stops.
Program structure
Same as functions, you have to put the definition of custom subroutines in interface
. To call a subroutine, use the call
statement with required arguments.
program custom
implicit none
real :: a,b,c,v1,v2
interface
subroutine solutions_quadratic_equations(a,b,c,s1,s2)
implicit none
real,intent(in) :: a,b,c
real,intent(out) :: s1,s2
end subroutine solutions_quadratic_equations
end interface
! use a custom function as a regular function
read *,a,b,c
call solutions_quadratic_equations(a,b,c,v1,v2)
print *,v1,v2
end program custom
subroutine solutions_quadratic_equations(a,b,c,s1,s2)
implicit none
real,intent(in) :: a,b,c
real,intent(out) :: s1,s2
real :: d
d = b**2 - 4*a*c
if(d>=0) then
s1 = (-b + sqrt(d))/(2*a)
s2 = (-b - sqrt(d))/(2*a)
else
print *,'b**2 - 4*a*c < 0'
stop
end if
end subroutine solutions_quadratic_equations
The scope of variables is the same as as functions. In the above case, the variables a
, b
, and c
are exclusively used in each program unit.
Summary
- An external procedure (function or subroutine) is independent of the main program.
- Variables are accessible in the program unit where they are defined.
- A function is defined with the
function
statement and a subroutine withsubroutine
. - In a definition of procedure, all the arguments should be declared as variables with
intent
. - A function should return a single value, but a subroutine does not have to return values or can return any number of values.
Exercises
- Write an external function to have 3 real numbers: \(x\) and \(\mu\), and \(\sigma^2\), and return the standardized value \(z=(x-\mu)/\sigma\).
- Write an external subroutine to accept one real number and print the logarithm of it.
Internal function and subroutines
If a function or a subroutine is tightly associated with another program unit, you can put the procedure inside the program unit. The included function or subroutine is called internal procedure.
One most significant advantage of the internal procedure is that it can access all the variables defined in the parent program-unit. In other words, the main program and the internal procedures share the same scope of variables. The internal procedures inherit all the variables and configurations (like implicit none
) defined in the main program.
Definition of internal procedure
basic form
An internal procedure is included in the main program. The definition of the internal procedure has the same form as an external one. The following is an internal version of solution-of-quadratic-equation subroutine, shown above.
program custom
implicit none
real :: a,b,c,v1,v2
read *,a,b,c
call solutions_quadratic_equations(a,b,c,v1,v2)
print *,v1,v2
contains
subroutine solutions_quadratic_equations(a,b,c,s1,s2)
real,intent(in) :: a,b,c
real,intent(out) :: s1,s2
real :: d
d = b**2 - 4*a*c
if(d>=0) then
s1 = (-b + sqrt(d))/(2*a)
s2 = (-b - sqrt(d))/(2*a)
else
print *,'b**2 - 4*a*c < 0'
stop
end if
end subroutine solutions_quadratic_equations
end program custom
You will see several rules in this program to use internal procedures.
- The internal procedure should be placed between
contains
andend program
. Multiple internal-procedures can be described. - The
contains
keyword should be at the end of the body of the main program. - Once the main program uses
implicit none
, this is also effective in all the internal procedures; each internal procedure does not have to haveimplicit none
. - The main program does not need the
interface
statement to use internal procedures.
In the above program, the internal subroutine still keeps its independence. All required variables come from the arguments, and two output variables will be returned through the arguments. There is 1 local variable in the internal subroutine, and this is accessible only in the subroutine (the main program cannot touch it).
Shared variables
When you try to share the variables between the main and the internal procedure, the program looks like this.
program custom
implicit none
real :: a,b,c,v1,v2
read *,a,b,c
call solutions_quadratic_equations()
print *,v1,v2
contains
subroutine solutions_quadratic_equations()
real :: d
d = b**2 - 4*a*c
if(d>=0) then
s1 = (-b + sqrt(d))/(2*a)
s2 = (-b - sqrt(d))/(2*a)
else
print *,'b**2 - 4*a*c < 0'
stop
end if
end subroutine solutions_quadratic_equations
end program custom
In this case, the variables a
, b
, c
, v1
, and v2
, which are not declared in the internal subroutine, will be recognized as shared variables with the main program. The internal subroutine does not have to use arguments to access the values; it can just read and write the variables in the main program. To call this subroutine, the user should still use ()
with the subroutine name. It is useful when the main program exclusively requires internal procedures.
Internal procedures in external procedures
Internal procedures can reside in any program unit. For example, an external function can include some internal procedures. It is useful if the internal procedure is a part of the program unit. The following example shows an external subroutine with an internal function.
program custom
implicit none
real :: a,b,c
interface
subroutine print_status(a,b,c)
implicit none
real,intent(in) :: a,b,c
ens subroutine print_status
end interface
read *,a,b,c
call print_status(a)
end program custom
subroutine print_status(a,b,c)
implicit none
real,intent(in) :: a,b,c
if(discriminant(a,b,c)>=0) then
print *,'The equation has real solutions.'
else
print *,'The equation has imaginary solutions.'
end if
contains
function discriminant(a,b,c) result(d)
real,intent(in) :: a,b,c
real :: d
d = b**2 - 4*a*c
end function discriminant
end subroutine print_status
Technically, the internal function discriminant
can access all the variables defined in print_status
(but in the above case, the variables are passed as the argument).
Which should be used, external or internal?
There are pros and cons to external and internal procedures. There are a few recommendations about which should be used in your case.
The external procedures are preferred if your procedure will be re-used in other places. The procedure should be independent and self-described in terms of the variable scope. The independent procedure is more comfortable to test and modify. The disadvantage is that you have to declare the interface of the procedure before using it.
The internal procedure is a good option if it is exclusively used by the program unit that includes the procedure. The procedure shares the same scope of variables as the parent program-unit. And therefore, the internal procedure is not portable when it depends on outside variables. Also, when you have many internal procedures, it becomes unclear which and when internal procedures change the variables defined in the parent unit. You need a custom rule for which variables should be shared between the procedures.
Because of the usability, in this tutorial, I am going to use external procedures mainly. The disadvantage of a redundant interface will be removed by using modules, which is introduced in the next chapter. Also, I use an internal procedure if it is the right way to do a job.
Summary
- A program unit can include internal procedures.
- All the variables and the
implicit none
status in the parent unit will be shared with the internal procedures. The internal procedure can still have its local variables. - The internal procedures stay after the
contains
statement in the parent unit. Any number of internal procedures can be included. - The internal procedures are exclusively used only by the parent program unit so that no
interface
is needed. - External procedures are useful if they will be re-used in different programs. Internal procedures are useful if it is closely related to the variables defined in the parent unit.
Exercises
- Rewrite the program of
cubicroot
using an internal function.
Various argument types
You can pass any objects, such as scalar, arrays, and derived-type variables, to functions and subroutines through arguments. In this section, I am going to use internal procedures just for simplicity, although each procedure is re-usable.
Arrays
To use an array as an argument, the user should specify its dimension. The following function accepts a real vector and returns the average of elements.
program custom
implicit none
real :: obs(5)
read *,obs
print *,'mean=',mean(obs)
contains
! assumed-shape array
function mean(x) return (m)
real,intent(in) :: x(:)
real :: m
integer :: n
n = size(x)
m = sum(x)/n
end function mean
end program custom
The argument is an assumed-shape array which declares only the dimension of an array with (:)
, and its actual size is separately available using some functions like size
. If a matrix (2-dimensional array) is passed, the declaration is like x(:,:)
. You need a local, integer variable (here n
) to get the size of the input array.
There is another style to pass an array to procedures.
program custom
implicit none
real :: obs(5)
read *,obs
print *,'mean=',mean(obs)
contains
! fixed-shape array
function mean(n,x) return (m)
integer,intent(in) :: n
real,intent(in) :: x(n)
real :: m
m = sum(x)/n
end function mean
end program custom
This style is called fixed-shape. In this method, the size of array x
is fixed, and the size is given another argument n
. The programmer is responsible for making sure that the actual array size equals to n
; if it is not true, the result of this function is unknown. In general, the fixed-share array is not recommended unless you have a definite reason to use it.
Characters
A string can be an argument, and the length is not necessarily specified.
program custom
implicit none
character(len=10) :: string
read *,string
print *,'Is the fiest letter alphabet? ',first_alphabet(string)
contains
function first_alphabet(s) return (yesno)
character(len=*),intent(in) :: s
logical :: yesno
integer :: p
p = scan(s(1:1),"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
if(p==1) then
yesno = .true.
else
yesno = .false.
end if
end function first_alphabet
end program custom
The length of the argument string can be len=*
, and the actual length is available by a function len
(or len_trim
). In the above example, the length is not obtained because it is not needed.
Derived types
A derived type is also supported as an argument.
program custom
implicit none
type animal
character(len=10) :: name
integer :: age
end type animal
type(animal) :: a
a%name = "beta-gamma"
a%age = 1
call print_info(a)
contains
subroutine print_info(x)
type(animal),intent(in) :: x
print *,"name=",x%name
print *,"age =",x%age
end subroutine print_info
end program custom
The input/output status intent(in)
affects all member variables of x
. In this case, the members are read-only.
Exercises
- Write a subroutine to print a matrix with a structure.
- Write a function to indicate whether the first two letters are alphabet or not.
Quick return
You may want to exit in the middle of a procedure quickly. The return
statement performs the quick return to the caller. To see the effect, we look back to the “alphabet” function.
function first_alphabet(s) return (yesno)
character(len=*),intent(in) :: s
logical :: yesno
integer :: p
! error check
if(len(s)==0) then
yesno = .true.
return
end if
! main body
p = scan(s(1:1),"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
if(p==1) then
yesno = .true.
else
yesno = .false.
end if
end function first_alphabet
It checks the length of s
, and if the length is 0 (i.e. ""
is given), the function is terminated to return .false.
. When the program meets return
, the remaining code will not be performed, and the focus immediately returns to the original caller.
Regarding the above program, there are many ways to result in the same logic, even without return
.
function first_alphabet(s) return (yesno)
character(len=*),intent(in) :: s
logical :: yesno
integer :: p
! error check
if(len(s)==0) then
yesno = .true.
else
! main body
p = scan(s(1:1),"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
if(p==1) then
yesno = .true.
else
yesno = .false.
end if
end if
end function first_alphabet
It is related more to a style of programming. Many people empirically know what a better style is, but it is not a topic in this tutorial. See some books dealing with programming style.
Exercises
There is no exercise in this subsection.
Back to index.html.