Welcome to a comprehensive guide on Object-Oriented Programming (OOP) in Modern Fortran. While Fortran is often associated with its procedural heritage, Fortran 2003 and later standards introduced powerful OOP features that rival those found in Java, C++, and D. This document explores how Fortran approaches OOP concepts and provides practical examples using the GNU Fortran Compiler (gfortran). By understanding Fortran's OOP capabilities, you'll be able to write more modular, maintainable, and reusable scientific and engineering code.
After completing this exercise, you will be able to:
public and private access modifiersextends keywordclass and select typegfortranObject-Oriented Programming is a paradigm that organizes code around "objects" that contain both data (variables) and functions (methods). Think of an object like a blueprint: a Drink type defines what properties all drinks have and what operations can be performed on them.
The core principles of OOP are:
Modern Fortran (2003 and later) supports all of these principles through derived types, type-bound procedures, type extension, and polymorphic variables.
| Feature | Java | C++ | D | Modern Fortran |
|---|---|---|---|---|
| Class/Type Definition | class ClassName |
class ClassName |
class ClassName |
type :: TypeName |
| Constructor | ClassName() |
ClassName() |
this() |
Interface + function |
| Destructor | Automatic (GC) | ~ClassName() |
Automatic or ~this() |
final :: cleanup |
| Inheritance | extends |
: |
: |
extends(ParentType) |
| Access Modifiers | public, private, protected |
public, private, protected |
public, private, protected |
public, private |
| Method Binding | Automatic | virtual keyword |
Automatic | procedure :: in contains |
| Polymorphism | Virtual by default | Must use virtual |
Virtual by default | class() + select type |
| Memory Management | Automatic (GC) | Manual (new/delete) |
Automatic or Manual | allocatable + auto dealloc |
| Abstract Types | abstract class |
Pure virtual classes | abstract keyword |
abstract + deferred |
| Constants | final keyword |
const keyword |
immutable keyword |
parameter attribute |
| This/Self Reference | this |
this-> |
this |
Passed argument (e.g., self) |
| Compilation | javac |
g++ |
gdc |
gfortran |
Let's compare how to define a basic class/type in all four languages:
public class Drink {
final int MAXIMUM = 10;
private String name;
public Drink(String brandName) {
name = brandName;
}
public String getName() {
return name;
}
}
class Drink {
private:
static const int MAXIMUM = 10;
string name;
public:
Drink(string brandName) {
name = brandName;
}
string getName() {
return name;
}
};
class Drink {
immutable int MAXIMUM = 10;
private string name;
this(string brandName) {
name = brandName;
}
string getName() {
return name;
}
}
type :: Drink
integer, parameter :: MAXIMUM = 10
private
character(len=50) :: name
contains
procedure :: get_name
end type Drink
interface Drink
module procedure create_drink
end interface
type :: TypeName instead of classparameter attribute instead of final or constprivate statement makes all components private by defaultcontains section using procedure ::interface block with the same name as the typeFortran uses a different approach to constructors than other OOP languages. Instead of a special constructor syntax, Fortran uses interface blocks to create constructor-like behavior:
public Drink(String name) {
this.name = name;
this.cost = 1.75;
}
public Drink(String name,
double cost) {
this.name = name;
this.cost = cost;
}
! Generic interface acts as constructor
interface Drink
module procedure create_drink
module procedure create_drink_cost
end interface
! Constructor function 1
function create_drink(name) result(d)
character(len=*), intent(in) :: name
type(Drink) :: d
d%name = name
d%cost = 1.75
end function
! Constructor function 2
function create_drink_cost(name, cost) &
result(d)
character(len=*), intent(in) :: name
real, intent(in) :: cost
type(Drink) :: d
d%name = name
d%cost = cost
end function
interface block with the same name as the type creates a generic constructor. When you write Drink("Coke"), Fortran automatically selects the appropriate constructor function based on the arguments provided. This is called generic resolution.
Here's how methods work in Fortran compared to other languages, using the vending machine example:
public void vend() {
if (available > 0) {
available -= 1;
consumed += 1;
moneyMade += cost;
System.out.println(
name + " Purchased.");
}
}
public double profit() {
return moneyMade;
}
subroutine vend(self)
class(Drink), intent(inout) :: self
if (self%available > 0) then
self%available = self%available - 1
self%consumed = self%consumed + 1
self%money_made = self%money_made &
+ self%cost
print *, trim(self%name), &
" Purchased."
end if
end subroutine vend
real function profit(self)
class(Drink), intent(in) :: self
profit = self%money_made
end function profit
this, Fortran requires explicitly passing the object as the first argument (conventionally named self)class(Drink) instead of type(Drink) enables polymorphism% operator instead of . (e.g., self%name not self.name)intent(in), intent(out), or intent(inout) for the self argumenttrim() to remove trailing spaces from character variablesNow let's look at a complete, working example using Modern Fortran and the GNU Fortran Compiler (gfortran). This program implements a simple vending machine that tracks drink sales and profits using separate module and program files.
When using Fortran with separate files, modules must be compiled before any program unit that uses them. The compiler creates .mod files that contain interface information needed by the use statement.
use that module in your main program. This ensures proper encapsulation and automatic interface generation.
Using modules for OOP in Fortran is crucial because:
public/private!===============================================================================
! drink_module.f90
!
! A module defining the Drink derived type with type-bound procedures.
! This demonstrates Object-Oriented Programming in Modern Fortran.
!
! OOP Features Demonstrated:
! - Derived type definition (similar to a class)
! - Type-bound procedures (methods)
! - Encapsulation with private components
! - Constructor using generic interface
! - Getter methods for controlled access
!
! Author: Student Name
! Course: Fortran Programming
!===============================================================================
module drink_module
implicit none
!---------------------------------------------------------------------------
! Access Control
! By default, make everything private, then explicitly export public items
!---------------------------------------------------------------------------
private
public :: Drink ! Export the type AND the constructor interface
!---------------------------------------------------------------------------
! Module-level constants (like static final in Java)
!---------------------------------------------------------------------------
integer, parameter :: MAXIMUM_CAPACITY = 10
real, parameter :: DEFAULT_DRINK_COST = 1.75
!---------------------------------------------------------------------------
! Derived Type Definition (similar to a class)
!
! The 'private' statement makes all components private by default.
! This enforces encapsulation - users must use methods to access data.
!---------------------------------------------------------------------------
type :: Drink
private ! All components are private
character(len=50) :: name ! Name of the drink
integer :: available = 0 ! Drinks currently in stock
integer :: consumed = 0 ! Total drinks sold
real :: drink_cost = DEFAULT_DRINK_COST ! Price per drink
real :: money_made = 0.0 ! Total revenue
contains
!-----------------------------------------------------------------------
! Type-Bound Procedures (methods)
! These are bound to the type and called using the % operator
!-----------------------------------------------------------------------
procedure :: vend ! Dispense a drink
procedure :: refill ! Restock to maximum capacity
procedure :: profit ! Get total revenue (rounded)
procedure :: drinks_sold ! Get number of drinks sold
procedure :: get_drink_name ! Get the drink name
procedure :: restock_amount ! Get number needed to restock
end type Drink
!---------------------------------------------------------------------------
! Generic Interface for Constructor
!
! This creates a "constructor" that can be called as Drink(name) or
! Drink(name, cost). The compiler selects the appropriate function
! based on the arguments provided.
!---------------------------------------------------------------------------
interface Drink
module procedure create_drink
module procedure create_drink_with_cost
end interface Drink
contains
!===========================================================================
! CONSTRUCTOR FUNCTIONS
!===========================================================================
!---------------------------------------------------------------------------
! create_drink: Constructor with name only (uses default cost)
!
! Arguments:
! brand_name - Name of the drink
!
! Returns:
! A new Drink object initialized with default values
!---------------------------------------------------------------------------
function create_drink(brand_name) result(d)
character(len=*), intent(in) :: brand_name
type(Drink) :: d
d%name = brand_name
d%consumed = 0
d%available = 0
d%money_made = 0.0
d%drink_cost = DEFAULT_DRINK_COST
end function create_drink
!---------------------------------------------------------------------------
! create_drink_with_cost: Constructor with name and custom cost
!
! Arguments:
! brand_name - Name of the drink
! cost - Custom price for this drink
!
! Returns:
! A new Drink object initialized with the specified cost
!---------------------------------------------------------------------------
function create_drink_with_cost(brand_name, cost) result(d)
character(len=*), intent(in) :: brand_name
real, intent(in) :: cost
type(Drink) :: d
d%name = brand_name
d%consumed = 0
d%available = 0
d%money_made = 0.0
d%drink_cost = cost
end function create_drink_with_cost
!===========================================================================
! TYPE-BOUND PROCEDURES (METHODS)
!===========================================================================
!---------------------------------------------------------------------------
! vend: Dispense a drink if available
!
! This is a subroutine (not a function) because it modifies the object
! and produces output as side effects.
!
! The 'self' argument:
! - Uses 'class(Drink)' for polymorphism support
! - Uses 'intent(inout)' because we modify the object
!---------------------------------------------------------------------------
subroutine vend(self)
class(Drink), intent(inout) :: self
if (self%available > 0) then
self%available = self%available - 1
self%consumed = self%consumed + 1
self%money_made = self%money_made + self%drink_cost
print '(A, A)', trim(self%name), " Purchased."
else
print *, ""
print *, " \\\\\\\\\\\\"
print *, " \ SOLD OUT \"
print *, " \\\\\\\\\\\\"
print *, ""
end if
end subroutine vend
!---------------------------------------------------------------------------
! refill: Restock drinks to maximum capacity
!---------------------------------------------------------------------------
subroutine refill(self)
class(Drink), intent(inout) :: self
self%available = MAXIMUM_CAPACITY
end subroutine refill
!---------------------------------------------------------------------------
! profit: Calculate and return the total revenue (rounded to cents)
!
! This is a function because it returns a value without side effects.
! Uses 'intent(in)' because we only read from the object.
!---------------------------------------------------------------------------
real function profit(self)
class(Drink), intent(in) :: self
! Round to 2 decimal places (cents)
profit = nint(self%money_made * 100.0) / 100.0
end function profit
!---------------------------------------------------------------------------
! drinks_sold: Return the number of drinks sold
!---------------------------------------------------------------------------
integer function drinks_sold(self)
class(Drink), intent(in) :: self
drinks_sold = self%consumed
end function drinks_sold
!---------------------------------------------------------------------------
! get_drink_name: Return the drink name
!
! This is a "getter" method - provides controlled access to private data
!---------------------------------------------------------------------------
function get_drink_name(self) result(name)
class(Drink), intent(in) :: self
character(len=50) :: name
name = self%name
end function get_drink_name
!---------------------------------------------------------------------------
! restock_amount: Return the number of drinks needed to reach full capacity
!---------------------------------------------------------------------------
integer function restock_amount(self)
class(Drink), intent(in) :: self
restock_amount = MAXIMUM_CAPACITY - self%available
end function restock_amount
end module drink_module
!===============================================================================
! machine.f90
!
! Main program demonstrating the use of the Drink derived type.
! This is the "driver" program that creates objects and calls methods.
!
! Demonstrates:
! - Using a module with 'use' statement
! - Creating objects using the constructor
! - Calling type-bound procedures (methods) with %
! - Working with multiple objects
!
! Author: Student Name
! Course: Fortran Programming
!===============================================================================
program machine
!---------------------------------------------------------------------------
! Use the drink_module
! This imports the Drink type and its constructor interface
!---------------------------------------------------------------------------
use drink_module
implicit none
!---------------------------------------------------------------------------
! Declare variables
! Note: We use type(Drink) to declare objects of our derived type
!---------------------------------------------------------------------------
type(Drink) :: drink1, drink2, drink3, drink4
integer :: total_sold
real :: total_profit
!---------------------------------------------------------------------------
! Create drink objects using the constructor
!
! The generic interface allows two forms:
! - Drink(name) -> uses default cost of $1.75
! - Drink(name, cost) -> uses specified cost
!
! Note the syntax: this looks like a function call, but it's actually
! invoking our constructor through the generic interface.
!---------------------------------------------------------------------------
drink1 = Drink("Coke")
drink2 = Drink("Sprite")
drink3 = Drink("Doctor Pepper")
drink4 = Drink("V8 Splash", 4.25) ! Custom price
!---------------------------------------------------------------------------
! Call the refill method on each drink
!
! Syntax: object%method()
! This is equivalent to: call refill(drink1) but more OOP-like
!---------------------------------------------------------------------------
call drink1%refill()
call drink2%refill()
call drink3%refill()
call drink4%refill()
!---------------------------------------------------------------------------
! Simulate purchases by calling vend() multiple times
!---------------------------------------------------------------------------
! Purchase 8 Cokes
call drink1%vend()
call drink1%vend()
call drink1%vend()
call drink1%vend()
call drink1%vend()
call drink1%vend()
call drink1%vend()
call drink1%vend()
! Purchase 6 Sprites
call drink2%vend()
call drink2%vend()
call drink2%vend()
call drink2%vend()
call drink2%vend()
call drink2%vend()
! Purchase 2 Doctor Peppers
call drink3%vend()
call drink3%vend()
! Attempt to purchase 11 V8 Splash (only 10 available - will show SOLD OUT)
call drink4%vend()
call drink4%vend()
call drink4%vend()
call drink4%vend()
call drink4%vend()
call drink4%vend()
call drink4%vend()
call drink4%vend()
call drink4%vend()
call drink4%vend()
call drink4%vend() ! This one will fail - SOLD OUT
!---------------------------------------------------------------------------
! Calculate totals using getter methods
!
! Note: For functions, we can use them in expressions directly
! drink1%drinks_sold() returns an integer
! drink1%profit() returns a real
!---------------------------------------------------------------------------
total_sold = drink1%drinks_sold() + drink2%drinks_sold() + &
drink3%drinks_sold() + drink4%drinks_sold()
total_profit = drink1%profit() + drink2%profit() + &
drink3%profit() + drink4%profit()
!---------------------------------------------------------------------------
! Display results
!---------------------------------------------------------------------------
print *, ""
print '(A, I0, A, F0.2)', "A Total of ", total_sold, &
" drinks were sold for a profit of $", total_profit
print *, ""
! Display restock information
! Note: trim() removes trailing spaces from character variables
print '(I0, A, A, A)', drink1%restock_amount(), " ", &
trim(drink1%get_drink_name()), " needed to restock machine"
print '(I0, A, A, A)', drink2%restock_amount(), " ", &
trim(drink2%get_drink_name()), " needed to restock machine"
print '(I0, A, A, A)', drink3%restock_amount(), " ", &
trim(drink3%get_drink_name()), " needed to restock machine"
print '(I0, A, A, A)', drink4%restock_amount(), " ", &
trim(drink4%get_drink_name()), " needed to restock machine"
end program machine
drink_module.f90 with the Drink module code (shown above)machine.f90 with the main program code (shown above)gfortran -c drink_module.f90
gfortran -c machine.f90
gfortran drink_module.o machine.o -o machine
Or compile everything in one command (module must come first!):
gfortran drink_module.f90 machine.f90 -o machine
./machine
.mod file (e.g., drink_module.mod) that the main program needs when it sees use drink_module.
| Approach | Command | When to Use |
|---|---|---|
| All at once | gfortran module.f90 main.f90 -o prog |
Small projects, quick testing |
| Separate compilation | gfortran -c module.f90gfortran -c main.f90gfortran *.o -o prog |
Larger projects, when only some files change |
| Using Makefile | make |
Professional projects, automated builds |
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Doctor Pepper Purchased.
Doctor Pepper Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
\\\\\\
\ SOLD OUT \
\\\\\\
A Total of 26 drinks were sold for a profit of $70.50
2 Coke needed to restock machine
4 Sprite needed to restock machine
8 Doctor Pepper needed to restock machine
0 V8 Splash needed to restock machine
In Fortran, modules serve as the container for derived types and their associated procedures:
| File | Declaration | Purpose |
|---|---|---|
| drink_module.f90 | module drink_module |
Contains the Drink type and all its methods |
| drink_module.f90 | public :: Drink |
Exports the type and constructor for external use |
| machine.f90 | use drink_module |
Imports the Drink type into the main program |
| Aspect | Type Components (Data) | Type-Bound Procedures (Methods) |
|---|---|---|
| Declaration | character(len=50) :: name |
procedure :: vend |
| Location | Before contains |
After contains in type definition |
| Access | self%name |
call self%vend() |
| Encapsulation | Usually private |
Usually public |
Notice that in the Drink type, all data members are declared private. This means they cannot be accessed directly from outside the module. Instead, users must use the public methods:
vend() - handles selling a drinkrefill() - restocks the drinksprofit() - calculates earningsdrinks_sold() - returns sales countget_drink_name() - returns the namerestock_amount() - returns needed inventory❌ Wrong:
gfortran machine.f90 drink_module.f90 -o machine
! Error: Module 'drink_module' not found
✅ Correct:
gfortran drink_module.f90 machine.f90 -o machine
! Module compiled first, then program
❌ Wrong (prevents polymorphism):
subroutine vend(self)
type(Drink), intent(inout) :: self ! type() - not polymorphic
! ...
end subroutine
✅ Correct (supports polymorphism):
subroutine vend(self)
class(Drink), intent(inout) :: self ! class() - polymorphic
! ...
end subroutine
❌ Wrong:
type :: Drink
private
character(len=50) :: name
! Missing 'contains' section!
end type Drink
subroutine vend(self) ! This is NOT bound to the type!
class(Drink), intent(inout) :: self
end subroutine
✅ Correct:
type :: Drink
private
character(len=50) :: name
contains
procedure :: vend ! Bind the procedure to the type
end type Drink
subroutine vend(self) ! Now this IS bound to Drink
class(Drink), intent(inout) :: self
end subroutine
❌ Wrong:
drink1%vend() ! Error: vend is a subroutine, needs 'call'
✅ Correct:
call drink1%vend() ! Correct for subroutines
! But functions can be used directly:
x = drink1%profit() ! No 'call' needed for functions
❌ Wrong (this is Java/C++/D syntax):
self.name = "Coke" ! Wrong operator
call drink1.vend() ! Wrong operator
✅ Correct (Fortran uses %):
self%name = "Coke" ! Correct: use %
call drink1%vend() ! Correct: use %
❌ Wrong:
subroutine vend(self)
class(Drink) :: self ! Missing intent - unclear behavior
end subroutine
✅ Correct:
subroutine vend(self)
class(Drink), intent(inout) :: self ! Clear: we read AND modify
end subroutine
real function profit(self)
class(Drink), intent(in) :: self ! Clear: we only read
end function
❌ Wrong (drink_module.f90):
module drink_class ! Module name
! ...
end module drink_class
! In machine.f90:
use drink_module ! ERROR: module drink_module not found
✅ Correct:
module drink_module ! Module name matches 'use' statement
! ...
end module drink_module
! In machine.f90:
use drink_module ! Correct: names match
Modify the vending machine program to replace the V8 Splash drink with a new beverage of your choice.
The current program includes V8 Splash at $4.25 as the fourth drink. Your task is to:
vend() method calls for each drinkgfortran and run it to ensure it works properlyIn machine.f90, change the line:
drink4 = Drink("V8 Splash", 4.25)
to your new drink with a new price, for example:
drink4 = Drink("Orange Juice", 3.50)
vend() calls (you can change the number of times each is called)machine.f90 (the module file doesn't need changes)gfortran drink_module.f90 machine.f90 -o machine
./machine
Before running your program, calculate what the total profit should be. Does your output match your prediction?
Hint: Total profit = (# of Cokes × $1.75) + (# of Sprites × $1.75) + (# of Dr Peppers × $1.75) + (# of your drink × your price)
Modern Fortran supports inheritance and polymorphism, though we haven't used them in the vending machine example. Here's a brief overview:
! Base type
type :: Beverage
character(len=50) :: name
real :: cost
contains
procedure :: display
end type Beverage
! Extended type (inherits from Beverage)
type, extends(Beverage) :: CaffeinatedDrink
real :: caffeine_mg ! Additional component
contains
procedure :: display => display_caffeinated ! Override parent method
end type CaffeinatedDrink
subroutine process_any_beverage(b)
class(Beverage), intent(in) :: b ! Accepts Beverage or any child type
! Use select type for type-specific behavior
select type(b)
type is (Beverage)
print *, "This is a regular beverage"
type is (CaffeinatedDrink)
print *, "This is caffeinated with", b%caffeine_mg, "mg"
class default
print *, "Unknown beverage type"
end select
end subroutine
contains section define methodsclass(TypeName) for polymorphic arguments, type(TypeName) for non-polymorphic% operator (not .)self argument must be explicitly declared in all methodsintent for the self argument (in, out, or inout)private for encapsulation and public to export interfacesuse themcall for subroutine methods: call obj%method()x = obj%method()// and trim() removes trailing spaces| Language | Strengths | Best For |
|---|---|---|
| Java | Write once, run anywhere; Strong ecosystem; Great for large projects | Enterprise applications, web backends, Android apps |
| C++ | High performance; Maximum control; Close to hardware | System software, games, performance-critical apps |
| D | Combines best of Java and C++; Modern features; Fast compilation | Systems programming, learning OOP, rapid development |
| Modern Fortran | Excellent array handling; Mature numerical libraries; Legacy code compatibility | Scientific computing, numerical simulation, HPC |