Functions & Subroutines
Functions vs subroutines
A function is called in an expression and yields one value; its name acts as the result variable. A subroutine is invoked with call, returns nothing by name, and communicates through its arguments. The rule of thumb from the scientific-computing style guides: use functions for pure math (y = my_sqrt(x)), and subroutines for anything that writes, allocates, or moves data.
program procedure_demo
implicit none
real :: a, b
a = 3.0
b = 4.0
print *, hypot2(a, b) ! function call inside an expression
call report(a, b) ! subroutine call as a statement
contains
! A function: returns one value through its name.
real function hypot2(x, y)
real, intent(in) :: x, y
hypot2 = sqrt(x*x + y*y)
end function hypot2
! A subroutine: performs an effect, returns nothing by name.
subroutine report(x, y)
real, intent(in) :: x, y
print '(a,2f6.2)', 'args: ', x, y
end subroutine report
end program procedure_demo
Notice the two spellings of the function header: legacy real function hypot2(x, y) and modern function hypot2(x, y) result(r) with an explicit result name, which is preferred when a result name clarifies the code or is needed for recursion.
INTENT: the argument contract
Fortran passes arguments by reference — the callee sees the caller's actual variable. intent(in) promises the callee only reads it (the compiler may copy it behind your back, enabling huge optimization wins), intent(out) declares the callee writes it fresh, and intent(inout) means read-modify-write. Declaring intent turns bugs like accidentally overwriting an input into compile-time errors.
Fig. 1 — INTENT is a legal contract the compiler enforces and optimizes against.
Optional and keyword arguments
Arguments may be declared optional, and every argument may be passed by keyword: call report(x=a, y=b). Keyword calling frees you from memorizing positional order and makes each argument self-documenting at the call site. The intrinsic present(name) tests at runtime whether an optional argument was supplied — the idiom for defaults.
module stats
implicit none
contains
function mean(x, mask) result(m)
real, intent(in) :: x(:)
logical, intent(in), optional :: mask(:) ! per-element filter
real :: m
logical :: keep(size(x))
if (present(mask)) then
keep = mask
else
keep = .true. ! default: use every element
end if
m = sum(x, mask=keep) / count(keep)
end function mean
end module stats
program optional_demo
use stats
implicit none
real :: data(5) = [1.0, 2.0, 3.0, 4.0, 100.0]
print *, mean(data) ! uses every element: 22.0
print *, mean(data, mask=data < 10.0) ! rejects the outlier: 2.5
end program optional_demo
Pure and elemental procedures
pure promises a procedure has no side effects: it reads arguments, computes, returns — it never writes globals or performs I/O. The compiler verifies the promise, and then do concurrent and OpenMP can safely call it from many threads at once. elemental is the sharper tool: an elemental function takes scalar arguments but is applied element-wise when given arrays — one body, both scalar and array callers:
module physics
implicit none
contains
! One body; callable with scalars AND with arrays.
elemental real function fahrenheit(c)
real, intent(in) :: c
fahrenheit = c * 9.0 / 5.0 + 32.0
end function fahrenheit
end module physics
program elemental_demo
use physics
implicit none
real :: city(3) = [-5.0, 12.0, 27.0]
print *, fahrenheit(city) ! whole array in one call
print *, fahrenheit(city(1)) ! scalar call, same body
end program elemental_demo
Recursion and placement
Functions that call themselves declare recursive (gfortran accepts it even without the keyword, but the standard and other compilers require it). The classic factorial is short but illuminating: watch the call stack grow, then unwind multiplying.
recursive function fact(n) result(r)
integer, intent(in) :: n
integer :: r
if (n <= 1) then
r = 1
else
r = n * fact(n - 1) ! the recursive step
end if
end function fact
Where procedures live decides how they are shared. Internal procedures sit in a unit's contains block and see the host's variables (host association) — convenient, but hidden from other files. Module procedures live in a module, are usable by any unit that uses the module, and their interfaces are checked automatically at every call site — the default choice for anything reused. External procedures are the pre-module legacy; they demand hand-written interface blocks and are the source of most "unexpected argument types" bugs in old code.
intent on every argument; math becomes elemental where possible, pure where it can be, and lives in a module the moment a second file needs it.