Exercises: Object-Oriented Fortran
These exercises drill the mechanics — extends, class, select type, abstract/deferred, final —
and, just as hard, the judgment about where object orientation belongs in numerical code. Several problems
deliberately ask you to decide not to use a feature; getting those right is as important as getting the
syntax right.
Difficulty: ⭐ warm-up · ⭐⭐ standard · ⭐⭐⭐ deeper. Solutions: worked solutions to the daggered (†)
and odd-numbered problems are in appendices/answers-to-selected.md; the computational "Design it" solution
is also runnable code in code/exercise-solutions.f90. Compile everything with gfortran -std=f2018 -Wall,
and predict every output before you compile — the whole book is written that way.
Part A — Warm-ups ⭐
10.1 † In one sentence each, distinguish a variable's declared type from its dynamic type. For
class(shape_t), allocatable :: s holding a circle_t, name each.
10.2 Why must a polymorphic local variable be allocatable or pointer? Write the one-line declaration
that does not compile and the two that do.
10.3 † A dog_t extends an animal_t. Name two things dog_t inherits automatically, and state how you
would call animal_t's version of an overridden speak binding from inside dog_t's version.
10.4 What does the abstract attribute forbid, and what does a deferred binding force? Give one
sentence each.
10.5 † A final subroutine's dummy argument is declared type(...), not class(...). Why does that
make sense — that is, why is finalization not a dispatched call?
10.6 True or false, with one sentence of justification: "You should give every derived type a final
procedure to free its allocatable components."
Part B — Type, Compile, and Run ⭐⭐
Predict the output first, then compile and check. Each is a complete idea in a few lines.
10.7 † With the abstract hierarchy of code/example-03-abstract-final.f90, predict the output of:
class(shape_t), allocatable :: s
allocate(s, source = rectangle_t(width = 5.0_dp, height = 5.0_dp))
print '(f9.5)', s%area()
10.8 Predict what this prints, and explain which binding runs and why:
type(circle_t) :: c ! from example-01
c%radius = 3.0_dp
call c%describe()
10.9 † Given three boxes holding a circle_t(radius=1), a rectangle_t(2,2), and a circle_t(radius=2),
predict the output of a loop that runs select type and prints 'circle' for type is (circle_t) and
'other' for class default.
10.10 Predict the exact sequence of printed lines:
type(managed_t), allocatable :: a, b
allocate(a); a%name = "one"
allocate(b); b%name = "two"
print '(a)', 'made both'
deallocate(a)
deallocate(b)
(Use the managed_t of example-03.) Then explain why deallocating explicitly makes the order predictable,
whereas relying on scope-exit finalization of two locals would not.
Part C — Find the Bug ⭐⭐
Each snippet fails to compile or misbehaves. Diagnose it precisely and give the fix.
10.11 † Won't compile:
type, abstract :: solver_t
contains
procedure(step_i), deferred :: step
end type
type(solver_t) :: s ! <-- here
10.12 Won't compile:
class(shape_t) :: s ! a local variable
allocate(s, source = circle_t(radius = 1.0_dp))
10.13 † Compiles, but the author is surprised that area "does nothing" for their new hexagon_t:
type, extends(shape_t) :: hexagon_t ! shape_t is concrete, area() returns 0
real(dp) :: side = 0.0_dp
end type
! ...they never wrote a hexagon_area and never bound it...
Why does h%area() return 0 instead of failing, and what one change to shape_t would have caught the
mistake at compile time?
10.14 The finalizer never seems to run:
subroutine managed_free(self)
class(managed_t), intent(inout) :: self ! <-- author used class
print *, 'freeing ', self%name
end subroutine
The compiler accepts a nearby definition but the type's final never fires. What is wrong with the dummy
declaration for a final procedure?
10.15 † This structure constructor for an extended type gives a "type mismatch" error. Why, and what is the fix?
allocate(coll(1)%obj, source = circle_t(2.0_dp)) ! circle_t extends shape_t(label)
Part D — Design It ⭐⭐ / ⭐⭐⭐
10.16 † (runnable — see code/exercise-solutions.f90.) Extend the abstract shape_t hierarchy with a
triangle_t (components base, height; area $= \tfrac12\,\text{base}\cdot\text{height}$). Build a
collection of a circle ($r=1$), a rectangle ($2\times3$), and your triangle ($\text{base}=4,\
\text{height}=5$), and print each area and the total through the common interface. Confirm the summing loop
does not mention any concrete type.
10.17 Add a second solver to the solver_t framework of §10.6: a decay_solver_t whose step multiplies
every interior point by $(1 - \alpha\,\Delta t)$ (an exponential-decay model). With $\alpha=1$, $\Delta t=0.1$,
and an interior point starting at $100$, hand-compute its value after 3 steps, then confirm your framework
runs it by changing only the allocate line in the driver.
10.18 † Design an abstract boundary_t with a deferred apply(self, fld) binding, and two extensions:
dirichlet_t (sets the edge to a fixed value) and neumann_t (copies the neighbouring interior value to the
edge, a zero-gradient condition). Explain how this lets the solver's boundary treatment vary without editing
the solver. (Numerics of these conditions are formalized in Chapter 24; here design the interface.)
10.19 Using the "array of boxes" idiom, design a container that holds a mixed list of solver_t
implementations and runs each one's step on the same field in turn. Why can this not be a bare
class(solver_t), allocatable :: sims(:) array?
Part E — Port It ⭐⭐
Translate the Python to modern Fortran, then note where the two languages differ in cost.
10.20 † Port this Python class hierarchy to Fortran (an abstract base with one deferred method and two
concrete subclasses), and say what Fortran forces you to declare that Python does not:
class Shape:
def area(self): raise NotImplementedError
class Circle(Shape):
def __init__(self, r): self.r = r
def area(self): return 3.141592653589793 * self.r**2
class Square(Shape):
def __init__(self, s): self.s = s
def area(self): return self.s * self.s
10.21 In Python, shapes = [Circle(1), Square(2)] mixes types in a list for free. Write the Fortran
equivalent, and explain in two sentences why Fortran needs a wrapper type where Python needs nothing — and
what Fortran buys in return.
Part F — Back of the Envelope ⭐⭐⭐
Order-of-magnitude reasoning. Show your work; the exact number is not the point.
10.22 † A stencil kernel does about 8 floating-point operations per cell, and a modern core sustains on
the order of $10^{10}$ flop/s. Suppose a dispatched method call costs ~20 cycles (~$7\times10^{-9}$ s at
3 GHz) and, worse, blocks vectorization so the arithmetic runs ~4× slower. Estimate the slowdown if you make
each of a $1000\times1000$ grid's cells a polymorphic object dispatched once per cell per step, versus a flat
kernel. Why does this justify the chapter's rule "keep class out of the hot loop"?
10.23 The same solver dispatches its step once per timestep over $10^5$ steps. Estimate the total time
spent in dispatch overhead (at ~20 cycles each) and compare it to a single 30-minute run. Is coarse-grained
polymorphism's cost measurable? What does this contrast (10.22 vs 10.23) tell you about where to put
abstraction?
10.24 † A heterogeneous container stores $N$ shapes as an array of boxes, each box holding an allocatable polymorphic component. Compared with a plain array of a single concrete type, roughly what extra costs (per element, and in access pattern) does the polymorphic container carry? Why does this matter more for a million small objects than for ten large ones?
Part G — Interleaved ⭐⭐
These reach back into Part I and the earlier Part II chapters.
10.25 † (Ch. 8, 9) The solver_base, heat_types, and heat_solver_oo modules form a small
hierarchy. Draw the use dependency graph and give a valid compilation order. Why can heat_solver_oo not
be compiled before solver_base?
10.26 (Ch. 5, 9) Inside heat_step, fld%u is a plain allocatable 2D array and the update loops
i (first index) innermost. Explain, using column-major order, why that loop nest is the fast one — and why
this is exactly the arithmetic you must keep monomorphic.
10.27 † (Ch. 6, 9) A type-bound procedure's passed object is class(t), not type(t). Connect this to
intent: write the passed-object declaration for a step that reads but does not modify the solver, and for
one that updates an internal step counter.
10.28 (Ch. 3, 8) The abstract interface for a deferred area needs real(dp) in scope. Show two valid
ways to make dp available inside the abstract interface body, and explain why the interface does not see
it automatically.
Solutions to the daggered and odd-numbered problems are in appendices/answers-to-selected.md; the runnable
"Design it" solution (10.16) is in code/exercise-solutions.f90. If you can compile 10.16, extend 10.17, and
diagnose all of Part C, you have the mechanics — and Part F is where you prove you have the judgment.