Chapter 6 — Key Takeaways (Procedures)
A one-page reference for subroutines, functions, intent, pure/elemental, recursion, and array
arguments. The two habits to carry away: an intent on every argument, and arrays passed as a(:,:).
Subroutine vs function
| Function | Subroutine | |
|---|---|---|
| Returns | exactly one value | zero, one, or many (via arguments) |
| Invoked | in an expression: y = f(x) |
with a statement: call s(...) |
| Use when | you compute a single value, no side effects | you act on state, return several results, or do I/O |
| Grammar | a noun (it is a value) | a verb (it does something) |
function mean(x) result(m) ! result clause names the return variable
real(dp), intent(in) :: x(:)
real(dp) :: m
m = sum(x) / real(size(x), dp)
end function mean
subroutine describe(x, avg, sd) ! two outputs -> subroutine
real(dp), intent(in) :: x(:)
real(dp), intent(out) :: avg, sd
! ...
end subroutine describe
intent — declare one on EVERY dummy argument
| Intent | Meaning | On entry | May the procedure write it? |
|---|---|---|---|
intent(in) |
read-only input | holds the caller's value | No (compile error if you try) |
intent(out) |
output, from scratch | undefined (old value gone) | Yes; must define it |
intent(inout) |
update in place | holds the caller's value | Yes |
- The compiler enforces it. Assigning to an
intent(in)argument, or passing a constant where a procedure will write, is caught at compile time — a safety net most languages lack. - Same word, same speed:
intent(in)is one of the guarantees that lets Fortran optimize (Ch. 27).
Optional and keyword arguments
subroutine relax(x_new, x_old, goal, factor)
real(dp), intent(out) :: x_new
real(dp), intent(in) :: x_old, goal
real(dp), intent(in), optional :: factor
real(dp) :: f
f = 1.0_dp
if (present(factor)) f = factor ! ALWAYS guard an optional with present
x_new = x_old + f * (goal - x_old)
end subroutine relax
call relax(a, 100.0_dp, 0.0_dp) ! omit optional
call relax(c, x_old=100.0_dp, goal=0.0_dp, factor=0.1_dp) ! keyword: any order
present(arg)— the one new intrinsic this chapter: true if the caller suppliedarg.- After the first keyword argument in a call, all following arguments must be keyword.
- Never read an absent optional's value; forward an absent optional straight through if you must.
pure and elemental
| Keyword | Promise | Payoff |
|---|---|---|
pure |
no side effects; all function args intent(in) |
compiler may reorder, hoist, deduplicate, parallelize the call |
elemental |
scalar args, applies element-by-element to arrays; implies pure |
write once for a scalar, use on any-shape arrays with no loop |
elemental function to_kelvin(c) result(k) ! to_kelvin(scalar) OR to_kelvin(array)
real(dp), intent(in) :: c
real(dp) :: k
k = c + 273.15_dp
end function to_kelvin
Internal procedures and recursion
- Internal procedure: defined after
contains; gets an explicit interface automatically (so optional/keyword/assumed-shape all work) plus host association (sees the host's variables). - Prefer passing arguments over relying on host association; it prevents accidental clobbering.
- Recursion: mark
recursive(optional under-std=f2018, but write it — it's clear) and must use aresultclause. Prefer adoloop in numerical kernels; recurse only for genuinely tree-shaped problems.
Array arguments — which kind, and when
| Kind | Declaration | Verdict |
|---|---|---|
| Assumed-shape | a(:,:) |
The default. Shape travels with the array; size/array ops/bounds-checking all work. Needs an explicit interface. |
| Explicit-shape | a(n, n) |
Occasionally — guaranteed contiguous; but you must pass n and can get it wrong. |
| Assumed-size (legacy) | a(*) |
Avoid. FORTRAN 77 relic; hides the true extent; size and array ops fail. Modernize to a(:,:). |
- Assumed-shape dummy lower bounds default to 1, whatever the caller's bounds were. Ask for a different
origin explicitly with
a(0:)only when the mathematics needs it.
Common pitfalls
intent(out)wipes the incoming value — useintent(inout)if you need it (e.g., an accumulator).- Using an absent optional without a
presentguard — undefined behavior. - Forgetting the
resultclause on a recursive function — the name becomes ambiguous. a(*)in new code — silently defeats bounds checking; usea(:).- Assuming an assumed-shape dummy keeps the caller's lower bounds — it renumbers from 1.
Numbers / rules worth memorizing
- One
intentper dummy argument — always. The cheapest bug prevention in the language. - Functions return one value; more outputs → a subroutine.
elemental⟹pure;purefunction args are allintent(in).- Optional/keyword/assumed-shape ⟹ need an explicit interface ⟹ put procedures in
containsor a module.
Compile flags (recurring)
gfortran -std=f2018 -Wall -O2 file.f90 -o prog — as before. Add -fcheck=all during development
(Ch. 13) to bounds-check assumed-shape arrays and catch use of undefined intent(out) arguments.
Project piece added this chapter
The heat solver's update becomes a real procedure:
subroutine step(field, alpha, dt) ! field intent(inout), assumed-shape (:,:)
A subroutine (updates in place), an intent on every argument, and field as an assumed-shape array so one
routine fits any plate size. The body is a placeholder — the real five-point stencil is Chapter 24 — but the
interface is now fixed for the rest of the book.