Functions and subroutines are fundamental building blocks in Fortran that allow programmers to modularize code, encapsulate logic, and perform computations or tasks efficiently. While both are essential, their purposes differ: functions return a value, whereas subroutines perform tasks without directly returning a value.
Adopting best practices when creating and using functions and subroutines is crucial for maintainability, readability, performance, and avoiding bugs. This post explores these best practices in depth with examples and guidelines.
1. Understanding Functions and Subroutines
1.1 Functions
- Designed to perform computations and return a single value
- Invoked directly in expressions
- Example:
program main
real :: x, result
x = 5.0
result = square(x)
print *, "Square of", x, "is", result
end program main
function square(y)
real, intent(in) :: y
real :: square
square = y**2
end function square
Explanation:
squarecomputes a value and returns it- Ideal for mathematical computations
1.2 Subroutines
- Perform tasks but do not return a value directly
- Modify arguments or produce side effects (like printing)
- Invoked using
call
program main
call greet("Fortran")
end program main
subroutine greet(name)
character(len=*), intent(in) :: name
print *, "Hello,", name
end subroutine greet
Explanation:
greetperforms a task (printing a message)- Subroutines are best for actions rather than calculations
2. Use intent(in/inout/out) to Clarify Arguments
Fortran allows specifying the intent of subroutine or function arguments:
intent(in)– Input only, cannot be modifiedintent(out)– Output, must be assigned within the routineintent(inout)– Both input and output
Example 1: Intent Usage
program main
integer :: a, b, sum
a = 5
b = 10
call add_numbers(a, b, sum)
print *, "Sum:", sum
end program main
subroutine add_numbers(x, y, result)
integer, intent(in) :: x, y
integer, intent(out) :: result
result = x + y
end subroutine add_numbers
Benefits:
- Clarifies argument purpose
- Prevents accidental modification of inputs
- Helps compiler catch errors
3. Keep Functions Focused on Computation
Functions should perform a single computation and return the result. Avoid combining computation with I/O or complex task logic.
Example 2: Good Function Design
function factorial(n)
integer, intent(in) :: n
integer :: factorial, i
factorial = 1
do i = 1, n
factorial = factorial * i
end do
end function factorial
Explanation:
- Function calculates factorial only
- No printing or other tasks inside the function
- Ensures reusability in expressions or calculations
4. Subroutines Handle Tasks
Subroutines are ideal for tasks like printing results, updating arrays, or performing operations on multiple variables.
Example 3: Subroutine for Array Update
program main
integer :: arr(5)
arr = (/1,2,3,4,5/)
call double_array(arr)
print *, "Doubled array:", arr
end program main
subroutine double_array(array)
integer, intent(inout) :: array(:)
integer :: i
do i = 1, size(array)
array(i) = array(i) * 2
end do
end subroutine double_array
Explanation:
- Performs an array operation
- No value is returned; modification is via intent(inout)
- Demonstrates task-oriented subroutine usage
5. Use Descriptive Names
Choosing clear and descriptive names improves readability and maintainability.
Example 4: Naming Conventions
function compute_average(values)
real, intent(in) :: values(:)
real :: compute_average
integer :: i
compute_average = 0.0
do i = 1, size(values)
compute_average = compute_average + values(i)
end do
compute_average = compute_average / size(values)
end function compute_average
Explanation:
- Function name clearly indicates purpose
- Arguments are named meaningfully (
values) - Avoid vague names like
f1ortemp
6. Modularize Code to Improve Readability and Reusability
Organize functions and subroutines into modules, which can be used across programs.
Example 5: Module with Multiple Routines
module math_utils
contains
function square(x)
real, intent(in) :: x
real :: square
square = x**2
end function square
subroutine double_array(arr)
integer, intent(inout) :: arr(:)
integer :: i
do i = 1, size(arr)
arr(i) = arr(i) * 2
end do
end subroutine double_array
end module math_utils
program main
use math_utils
real :: num
integer :: arr(5)
num = 4.0
print *, "Square of", num, "is", square(num)
arr = (/1,2,3,4,5/)
call double_array(arr)
print *, "Doubled array:", arr
end program main
Benefits:
- Functions and subroutines grouped logically
- Reusable across multiple programs
- Improves readability and maintainability
7. Avoid Excessive Recursion
While Fortran allows recursion with the recursive keyword, excessive recursion can lead to stack overflow.
Example 6: Recursive Factorial
recursive function factorial(n) result(res)
integer, intent(in) :: n
integer :: res
if (n <= 1) then
res = 1
else
res = n * factorial(n-1)
end if
end function factorial
Guidelines:
- Use recursion sparingly for clarity or mathematical definitions
- Prefer loops for large iterations
- Always test recursion depth for safety
8. Additional Best Practices
- Document routines – Describe purpose, inputs, outputs
- Use optional arguments – Increases flexibility
- Check argument sizes – Especially for arrays
- Avoid global variables – Use arguments instead
- Combine functions and subroutines logically – Keep computation and tasks separate
- Test individual routines – Unit testing ensures correctness
9. Advanced Example: Temperature Conversion Module
module temp_utils
contains
function celsius_to_fahrenheit(c)
real, intent(in) :: c
real :: celsius_to_fahrenheit
celsius_to_fahrenheit = c * 9.0 / 5.0 + 32.0
end function celsius_to_fahrenheit
subroutine print_temperature(c)
real, intent(in) :: c
print *, "Temperature in Celsius:", c
print *, "Temperature in Fahrenheit:", celsius_to_fahrenheit(c)
end subroutine print_temperature
end module temp_utils
program main
use temp_utils
call print_temperature(25.0)
end program main
Explanation:
- Function handles computation
- Subroutine handles printing task
- Shows separation of computation and output
10. Handling Arrays in Functions and Subroutines
Example 7: Array Average Function
function compute_average(arr) result(avg)
real, intent(in) :: arr(:)
real :: avg
integer :: i
avg = 0.0
do i = 1, size(arr)
avg = avg + arr(i)
end do
avg = avg / size(arr)
end function compute_average
Example 8: Array Scaling Subroutine
subroutine scale_array(arr, factor)
real, intent(inout) :: arr(:)
real, intent(in) :: factor
integer :: i
do i = 1, size(arr)
arr(i) = arr(i) * factor
end do
end subroutine scale_array
Best Practice:
- Functions return computation results
- Subroutines perform tasks on arrays or outputs
11. Practical Applications
- Scientific computing – Modular mathematical routines
- Engineering simulations – Modular tasks for grid computations
- Data analysis – Functions for metrics, subroutines for reporting
- User interface – Subroutines for output formatting
- Reusable modules – Standardized utilities for multiple programs
Leave a Reply