Exercises: Derived Types
These exercises make you design with types, not just recognize them. You will define your own types,
give them methods, predict what programs print, hunt bugs that come from the type/class distinction and
from shallow copies, and start turning the heat solver's loose variables into a real data structure. Type
and compile every code problem — the point of the chapter is that the compiler now checks your data design
for you, and you should feel it doing so.
Difficulty: ⭐ warm-up · ⭐⭐ standard · ⭐⭐⭐ deeper. Solutions: worked solutions to the daggered (†)
and odd-numbered problems are in appendices/answers-to-selected.md; the computational ones are also
compilable in code/exercise-solutions.f90. Try every problem before you look.
Part A — Warm-ups ⭐
9.1 † In one sentence each, define derived type and component, and give the operator used to access a component.
9.2 Write the declaration of a derived type interval with two real(dp) components lo and hi.
Then write a declaration of a variable i of that type and a structure-constructor assignment setting it to
the interval $[0, 1]$.
9.3 † What is the single most important rule about the passed-object dummy argument of a type-bound procedure? State it precisely.
9.4 Explain the difference between pass, pass(name), and nopass on a type-bound procedure, in one
sentence each.
9.5 † In one sentence, state the key behavioural difference between an allocatable component and a
pointer component when a derived-type variable is assigned with b = a.
Part B — Type, Compile, and Run ⭐⭐
Predict the exact output first, then compile and check. If your prediction is wrong, find out why before moving on — that gap is where the learning is.
9.6 Predict the output:
program p6
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
type :: box
real(dp) :: w = 1.0_dp, h = 2.0_dp
end type box
type(box) :: b
print '(f6.2)', b%w * b%h
b = box(3.0_dp, 4.0_dp)
print '(f6.2)', b%w * b%h
end program p6
9.7 † Predict the output, paying attention to the nested access:
program p7
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
type :: vec2
real(dp) :: x, y
end type vec2
type :: segment
type(vec2) :: a, b
end type segment
type(segment) :: s
s = segment(vec2(0.0_dp, 0.0_dp), vec2(3.0_dp, 4.0_dp))
print '(f6.2)', sqrt((s%b%x - s%a%x)**2 + (s%b%y - s%a%y)**2)
end program p7
9.8 Predict the output, then explain why the second printed value is what it is (this is the deep-copy question):
program p8
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
type :: bag
real(dp), allocatable :: v(:)
end type bag
type(bag) :: a, c
a%v = [1.0_dp, 2.0_dp, 3.0_dp]
c = a
c%v(1) = 99.0_dp
print '(f6.2)', a%v(1)
print '(f6.2)', c%v(1)
end program p8
9.9 † Predict the output. (The method is called through the object; the object is passed as self.)
module m9
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
type :: acct
real(dp) :: balance = 0.0_dp
contains
procedure :: deposit => acct_deposit
end type acct
contains
subroutine acct_deposit(self, amount)
class(acct), intent(inout) :: self
real(dp), intent(in) :: amount
self%balance = self%balance + amount
end subroutine acct_deposit
end module m9
program p9
use m9
implicit none
type(acct) :: a
call a%deposit(100.0_dp)
call a%deposit(25.0_dp)
print '(f8.2)', a%balance
end program p9
9.10 Predict the output of this array-of-derived-types program, then explain what sum(swarm%mass)
does:
program p10
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
type :: body
real(dp) :: mass
end type body
type(body) :: swarm(3)
swarm(1)%mass = 1.0_dp
swarm(2)%mass = 2.0_dp
swarm(3)%mass = 3.0_dp
print '(f6.2)', sum(swarm%mass)
end program p10
Part C — Design It ⭐⭐
These ask you to build types from scratch. Each has a compilable reference solution; write yours first.
9.11 † Define a derived type rectangle with real(dp) components width and height, and give it two
type-bound functions, area and perimeter. Write a program that builds a $3 \times 4$ rectangle and
prints both. (Reference: code/exercise-solutions.f90.)
9.12 Define a type grid_cell with a type(vec3) component center, a real(dp) component volume,
and a real(dp), dimension(6) component flux. Add a type-bound function net_flux(self) returning the
sum of the six face fluxes. Test it with fluxes [1, -1, 2, -2, 3, -3] (net flux $0$).
9.13 † Define a type vec2 with components x, y and two type-bound procedures: a function
add(self, other) returning a new vec2, and a function norm(self) returning $\sqrt{x^2 + y^2}$. In a
program, add $(3, 4)$ and $(1, 2)$ and print the sum and the norm of $(3, 4)$. (Reference:
code/exercise-solutions.f90.)
9.14 Add a nopass type-bound function zero() to your vec2 that returns the zero vector, and call it
as v%zero(). Explain why nopass is the right choice here.
9.15 † Design a running_stat accumulator type holding an integer count and the running sum and
sum-of-squares, with type-bound procedures push(self, x), mean(self), and var(self) (population
variance $\overline{x^2} - \overline{x}^2$). Feed it the sample [2, 4, 4, 4, 5, 5, 7, 9] and print the
count, mean, and variance. Verify by hand. (Reference: code/exercise-solutions.f90.)
9.16 Give your running_stat a user-defined constructor (an interface running_stat mapping to a
function) that returns a fresh, zeroed accumulator, so you can write st = running_stat(). What does the
default structure constructor already give you here, and why might the named constructor still be worth it?
Part D — Find the Bug ⭐⭐
Each snippet is wrong. Say what the compiler or the result will do, and give the fix.
9.17 † Won't compile. Why?
type :: circle
real(dp) :: r
contains
procedure :: area => circle_area
end type circle
! ...
pure function circle_area(self) result(a)
type(circle), intent(in) :: self ! <-- here
real(dp) :: a
a = 3.14159265358979_dp * self%r**2
end function circle_area
9.18 Compiles, but crashes or gives garbage at run time. Why?
type :: bag
real(dp), allocatable :: v(:)
end type bag
type(bag) :: b
b%v(1) = 10.0_dp ! <-- here
9.19 † The intent is for q to be an independent copy, but changing q also changes p. What kind of
component must data be, and how would you fix it?
type :: table
real(dp), pointer :: data(:)
end type table
type(table) :: p, q
allocate(p%data(3)); p%data = 1.0_dp
q = p ! shallow: q%data points at p%data
q%data(1) = 5.0_dp ! also changes p%data(1)!
9.20 Won't compile. The constructor call does not match the type. What is wrong?
type :: point3
real(dp) :: x, y, z
end type point3
type(point3) :: p
p = point3(1.0_dp, 2.0_dp) ! <-- here
Part E — Port It ⭐⭐
Translate the given code to modern Fortran using a derived type, then note what the type buys you over the original.
9.21 † Port this Python @dataclass to a Fortran derived type with an equivalent method:
from dataclasses import dataclass
from math import hypot
@dataclass
class Particle:
x: float
y: float
mass: float
def speed_from(self, vx, vy): # kinetic energy helper
return 0.5 * self.mass * (vx**2 + vy**2)
9.22 Port this C struct and function to a Fortran derived type with a type-bound procedure. Note the
one thing Fortran's version gives you that the C version does not (think about copying the whole struct).
struct Complexish { double re, im; };
double magnitude(struct Complexish z) { return sqrt(z.re*z.re + z.im*z.im); }
9.23 † A colleague models 1000 particles with six parallel arrays: px(1000), py(1000), pz(1000),
vx(1000), vy(1000), vz(1000). Rewrite the declarations two ways — (a) as an array of a particle
derived type (Array-of-Structures), and (b) as one particles type holding six component arrays
(Structure-of-Arrays). Say, in one sentence, when each layout is preferable (you will measure it in
Part VII).
Part F — Back of the Envelope ⭐⭐⭐
Order-of-magnitude reasoning about memory and layout. Show your work.
9.24 † A particle type holds three real(dp) positions, three real(dp) velocities, and one
real(dp) mass. How many bytes is one particle (a real(dp) is 8 bytes)? How much memory does an array of
1,000,000 particles occupy? Give the answer in megabytes (take $1\ \text{MB} = 10^{6}$ bytes).
9.25 You store the heat field as real(dp), allocatable :: u(:,:) inside a field_t. For a
$4096 \times 4096$ grid, how much memory does u occupy? If the solver keeps two fields (current and
next time step), what is the total? Would single precision (real32, 4 bytes) halve it?
9.26 † Array-of-Structures vs Structure-of-Arrays. A loop needs only the mass of all 1,000,000
particles from Exercise 9.24. In the Array-of-Structures layout, consecutive masses are 56 bytes apart in
memory; in a Structure-of-Arrays layout they are 8 bytes apart (contiguous). Explain, in terms of cache
lines (typically 64 bytes), why the second layout can be dramatically faster for this loop, and estimate the
ratio of useful-to-fetched bytes for each.
Part G — Interleaved ⭐⭐
Mixing this chapter with earlier ones. These are the retrieval reps that make it stick.
9.27 † (with Chapter 8) Put your rectangle type from 9.11 in a
module shapes with private/public so that only the type name (and its methods, via objects) are
visible, and the underlying function rect_area cannot be called by name from outside. Show the module
skeleton and explain what private plus public :: rectangle achieves.
9.28 (with Chapter 5) Given
type(field_t) :: f with f%u(:,:) allocated $nx \times ny$, write a single whole-array statement that
sets every interior temperature to the average of the whole field's current values, and a statement that
holds column ny at $100$. Which array intrinsic gives you the mean?
9.29 † (with Chapter 6) Your type-bound
functions are marked pure. State what pure promises the compiler, and why a method like norm or area
qualifies while a method like deposit (which modifies self) does not.
9.30 (with Chapter 3 and the
Project) Refactor a driver that currently declares five separate variables — nx, ny, dx, dy and
u(:,:) — to instead declare a single type(field_t) :: f and call f%init(...). List two categories of
bug this refactor makes impossible to write.
Solutions to the daggered and odd-numbered problems are in appendices/answers-to-selected.md; the
compilable ones (9.11, 9.13, 9.15) are in code/exercise-solutions.f90, each with hand-computed expected
output. Design problems accept any correct type layout — the reference solution is one good answer, not the
only one.