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 supplied arg.
  • 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 a result clause. Prefer a do loop 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 — use intent(inout) if you need it (e.g., an accumulator).
  • Using an absent optional without a present guard — undefined behavior.
  • Forgetting the result clause on a recursive function — the name becomes ambiguous.
  • a(*) in new code — silently defeats bounds checking; use a(:).
  • Assuming an assumed-shape dummy keeps the caller's lower bounds — it renumbers from 1.

Numbers / rules worth memorizing

  • One intent per dummy argument — always. The cheapest bug prevention in the language.
  • Functions return one value; more outputs → a subroutine.
  • elementalpure; pure function args are all intent(in).
  • Optional/keyword/assumed-shape ⟹ need an explicit interface ⟹ put procedures in contains or 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.