Function and subroutine

Yutaka Masuda

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.

Figure: External procedure as a separate program unit

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.

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

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

Exercises

  1. Write an external function to have 3 real numbers: \(x\) and \(\mu\), and \(\sigma^2\), and return the standardized value \(z=(x-\mu)/\sigma\).
  2. 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.

Figure: Internal procedure as a sub-program unit

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.

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

Exercises

  1. 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

  1. Write a subroutine to print a matrix with a structure.
  2. 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.