Program Structure
Hello, World
program hello
implicit none
print *, "Hello, World!"
end program hello print("Hello, World!") A Fortran program needs a
program name ... end program name wrapper and a compile step before it can run. R has neither: a script is just a sequence of top-level expressions, and print() writes a value with no ceremony at all.Comments
program comments_demo
implicit none
! This is a full-line comment
integer :: total ! This is a trailing comment
total = 10
print *, total
end program comments_demo # This is a full-line comment
total <- 10 # This is a trailing comment
print(total) Fortran comments use
!; R comments use # — both run to the end of the line, and both languages have no block-comment syntax at all.Compile-and-Run vs. Interpreted
program no_compile_step_demo
implicit none
integer :: value
value = 42
print *, value
end program no_compile_step_demo value <- 42
print(value) Fortran must be compiled to machine code before it runs — a separate step that catches type errors ahead of time. R is interpreted line by line with no compile step:
Rscript runs the file directly, and type errors surface only when the offending line actually executes.IMPLICIT NONE vs. No Declarations At All
program implicit_none_demo
implicit none
integer :: count
real :: average
count = 5
average = 3.5
print *, count, average
end program implicit_none_demo count <- 5
average <- 3.5
print(count)
print(average) Fortran with
implicit none requires every variable to be declared with an explicit type before use. R requires no declaration whatsoever — a name springs into existence the moment it is assigned, and its type is whatever value it currently holds. R variables can even change type across their lifetime with no error, something Fortran's static typing forbids entirely.= vs. <-
program assignment_demo
implicit none
integer :: count
count = 5
count = count + 1
print *, count
end program assignment_demo count <- 5
count <- count + 1
print(count) Fortran assigns with a bare
=. R's idiomatic assignment operator is <- (a left-pointing arrow made of two characters) — R also accepts = for assignment at the top level, but <- is the convention nearly every R programmer follows, since = is reserved for named function arguments in most style guides.Variables & Types
No True Scalars — Everything Is a Vector
program scalar_demo
implicit none
integer :: single_value
single_value = 42
print *, single_value
end program scalar_demo singleValue <- 42
print(length(singleValue))
print(singleValue) Fortran has genuine scalar types distinct from arrays — an
integer variable is not a 1-element array. R has no scalar type at all: 42 is actually a numeric vector of length 1, and length(singleValue) confirms it. Every R value is a vector; what looks like a scalar is simply the length-1 case.INTEGER/REAL Distinction vs. Everything Is a Double
program integer_real_demo
implicit none
integer :: whole_number
real :: fractional_number
whole_number = 5
fractional_number = 5.0
print *, whole_number
print *, fractional_number
end program integer_real_demo wholeNumber <- 5
fractionalNumber <- 5.0
print(wholeNumber)
print(fractionalNumber)
print(is.double(wholeNumber)) Fortran maintains a strict distinction between
integer and real types, chosen explicitly at declaration. R's ordinary number literals — even one that looks like a whole number, such as 5 — are always the double type unless explicitly marked otherwise with an L suffix (5L). This matters for arithmetic: R division always produces a double, never truncating the way Fortran integer division does.LOGICAL vs. Logical
program logical_demo
implicit none
logical :: is_ready
is_ready = .true.
if (is_ready) then
print *, "Ready"
end if
end program logical_demo isReady <- TRUE
if (isReady) {
print("Ready")
} Fortran's boolean literals are
.true./.false., surrounded by dots. R's are the bare words TRUE/FALSE (or the shorthand T/F, though R style guides discourage the shorthand since T and F can be reassigned as ordinary variable names).⚠ No Compile-Time Type Checking
program type_safety_demo
implicit none
integer :: count
count = 5
! count = "hello" ! compile-time error: type mismatch
print *, count
end program type_safety_demo count <- 5
print(count)
count <- "hello" # perfectly legal — count now holds a string
print(count) Fortran's compiler rejects assigning a string to an
integer variable before the program ever runs. R has no such check: reassigning count from a number to a string is completely legal, and any bug this introduces only appears later, when code that expects a number receives a string instead — often far from where the reassignment happened.Vectors & Arrays
Array Literal vs. c()
program array_literal_demo
implicit none
integer :: numbers(4)
numbers = [1, 2, 3, 4]
print *, numbers
end program array_literal_demo numbers <- c(1, 2, 3, 4)
print(numbers) Fortran builds an array literal with square brackets:
[1, 2, 3, 4]. R has no array-literal syntax — every vector is built by calling c() ("combine"), which concatenates its arguments into a single vector.Whole-Array Arithmetic (Both Languages Agree)
program vectorized_demo
implicit none
real :: prices(4)
prices = [10.0, 20.0, 30.0, 40.0]
prices = prices * 0.9
print *, prices
end program vectorized_demo prices <- c(10, 20, 30, 40)
prices <- prices * 0.9
print(prices) Unlike Pascal or C, both Fortran and R apply arithmetic element-wise to a whole vector with no loop needed —
prices * 0.9 scales every element in a single expression in both languages. This is the deepest structural similarity between the two, and it is exactly why the sharper contrast on this page is about shape-matching rules, not about whether vectorization exists at all.1-Based Indexing (Both Languages Agree)
program one_based_demo
implicit none
integer :: letters(3)
letters = [65, 66, 67]
print *, letters(1)
print *, letters(3)
end program one_based_demo letters <- c(65, 66, 67)
print(letters[1])
print(letters[3]) Another point of agreement: both Fortran and R index from 1 by default, matching mathematical convention. The first element of a 3-element vector is index 1 and the last is index 3 in both languages — a Fortran programmer's indexing instincts transfer directly to R, unlike the adjustment needed for Python, C, or Ruby.
⚠ Negative Indices Exclude, Not Count from the End
program negative_index_demo
implicit none
integer :: numbers(3)
numbers = [10, 20, 30]
! Fortran has no negative-index syntax; you would use
! ubound(numbers, 1) to reach the last element instead
print *, numbers(ubound(numbers, 1))
end program negative_index_demo numbers <- c(10, 20, 30)
# In R, a negative index EXCLUDES that position
print(numbers[-1])
# To get the last element instead:
print(numbers[length(numbers)]) Fortran has no negative-index syntax at all — reaching the last element means calling
ubound() explicitly. R does accept negative indices, but they mean something a Fortran programmer would not expect: numbers[-1] returns every element except the first, not the last element. Getting the actual last element in R requires numbers[length(numbers)] or tail(numbers, 1).Array Slicing vs. Vector Slicing
program slicing_demo
implicit none
integer :: numbers(5)
numbers = [10, 20, 30, 40, 50]
print *, numbers(2:3)
end program slicing_demo numbers <- c(10, 20, 30, 40, 50)
print(numbers[2:3]) Fortran's
numbers(2:3) and R's numbers[2:3] are visually near-identical and semantically identical: both select the 2nd and 3rd elements, both are inclusive on both ends, and both use 1-based positions.⚠ Vector Recycling vs. Strict Array Conformance
program conformance_demo
implicit none
integer :: a(4), b(4)
a = [1, 2, 3, 4]
! Fortran requires matching shapes for elementwise operations —
! this would be a compile-time or runtime shape-mismatch error:
! b = [10, 20] ! shapes (4) and (2) do not conform
b = [10, 20, 10, 20] ! must supply matching shape explicitly
print *, a + b
end program conformance_demo a <- c(1, 2, 3, 4)
b <- c(10, 20)
print(a + b) # b is "recycled": 10 20 10 20 This is the headline difference between two otherwise deeply similar languages. Fortran requires elementwise operands to have matching shapes — mismatched array extents are a compile-time or runtime error, full stop, with no silent workaround. R instead recycles the shorter vector, repeating its elements until it matches the longer one's length, so
c(1,2,3,4) + c(10,20) silently produces 11 22 13 24. R issues a warning only when the longer length is not an exact multiple of the shorter — otherwise recycling happens with no signal at all, a frequent and hard-to-spot source of R bugs that Fortran's stricter rule would catch immediately.Logical Indexing vs. WHERE
program where_demo
implicit none
integer :: numbers(5)
logical :: mask(5)
numbers = [5, 12, 3, 20, 8]
mask = numbers > 10
print *, pack(numbers, mask)
end program where_demo numbers <- c(5, 12, 3, 20, 8)
big <- numbers[numbers > 10]
print(big) Fortran's
pack(array, mask) intrinsic extracts the elements where a logical mask is true — conceptually the same operation as Fortran's where construct, but returning a new (possibly shorter) array instead of assigning in place. R reaches the identical result by indexing a vector with a logical vector: numbers[numbers > 10] builds the mask and filters in one expression.SUM/MAXVAL Intrinsics vs. Built-In Statistics
program vector_stats_demo
implicit none
integer :: numbers(6)
real :: mean_value
numbers = [4, 8, 15, 16, 23, 42]
mean_value = real(sum(numbers)) / size(numbers)
print *, "Sum:", sum(numbers)
print *, "Max:", maxval(numbers)
print *, "Mean:", mean_value
end program vector_stats_demo numbers <- c(4, 8, 15, 16, 23, 42)
print(sum(numbers))
print(max(numbers))
print(mean(numbers)) Fortran's
sum() and maxval() intrinsics map onto R's sum() and max() almost exactly, right down to the function name for sum. R goes one step further with a dedicated mean() built directly into the base language — Fortran has no intrinsic mean function, since computing one is only ever a single extra division away from sum() and size().Arithmetic
Integer Division & MOD vs. %/% and %%
program division_demo
implicit none
integer :: numerator, denominator, quotient, remainder_value
numerator = 17
denominator = 5
quotient = numerator / denominator
remainder_value = mod(numerator, denominator)
print *, "Quotient:", quotient
print *, "Remainder:", remainder_value
end program division_demo numerator <- 17
denominator <- 5
quotient <- numerator %/% denominator
remainderValue <- numerator %% denominator
print(quotient)
print(remainderValue) Fortran's plain
/ performs integer division when both operands are integers. R's / always produces a double (since R numbers default to double) — integer division requires the dedicated %/% operator, and remainder requires %%. Ordinary / in R on 17 and 5 gives 3.4, not 3, which surprises a Fortran programmer's reflexes.** vs. ^
program exponent_demo
implicit none
real :: result_value
result_value = 2.0 ** 10
print *, "2^10 =", result_value
end program exponent_demo resultValue <- 2 ^ 10
print(resultValue) Fortran's exponentiation operator is
**. R's is ^ — both languages have a dedicated infix exponentiation operator (unlike Pascal, which has neither and requires a Power() function call instead).Math Intrinsics vs. Base Math Functions
program math_functions_demo
implicit none
print *, "sqrt(144):", sqrt(144.0)
print *, "abs(-42):", abs(-42)
print *, "sin(0.0):", sin(0.0)
end program math_functions_demo print(sqrt(144))
print(abs(-42))
print(sin(0)) sqrt, abs, and the trigonometric functions are intrinsic in Fortran and built into base R with the same names and no import needed in either language — one of the closer one-to-one mappings on this page.NINT/INT vs. round/trunc
program rounding_demo
implicit none
real :: value
value = 3.7
print *, "nint:", nint(value)
print *, "int (truncate):", int(value)
print *, "floor:", floor(value)
print *, "ceiling:", ceiling(value)
end program rounding_demo value <- 3.7
print(round(value))
print(trunc(value))
print(floor(value))
print(ceiling(value)) Fortran's
nint()/int()/floor()/ceiling() map directly onto R's round()/trunc()/floor()/ceiling() — same four operations, nearly the same names. One subtlety: R's round() uses "round half to even" (banker's rounding) for values exactly halfway between two integers, which can surprise a Fortran programmer expecting nint()'s round-half-away-from-zero behavior.KIND System vs. No Precision Control
program kind_demo
use iso_fortran_env, only: real32, real64
implicit none
real(kind=real32) :: single_precision
real(kind=real64) :: double_precision_value
single_precision = 3.14_real32
double_precision_value = 3.14159265358979_real64
print *, single_precision
print *, double_precision_value
end program kind_demo # R has no separate single-precision numeric type at all —
# every plain number is a 64-bit double, always.
value <- 3.14159265358979
print(value) Fortran's KIND system lets a program choose 32-bit or 64-bit precision deliberately, which matters for memory layout in large scientific arrays. R offers no equivalent control at all — every ordinary numeric value is a 64-bit double, with no lower-precision option, since R was designed for interactive statistical analysis rather than memory-constrained numerical computing.
Strings
String Concatenation (//) vs. paste()
program concat_demo
implicit none
character(len=20) :: first_name, last_name
character(len=41) :: full_name
first_name = "Alice"
last_name = "Smith"
full_name = trim(first_name) // " " // trim(last_name)
print *, trim(full_name)
end program concat_demo firstName <- "Alice"
lastName <- "Smith"
fullName <- paste(firstName, lastName)
print(fullName) Fortran concatenates with the dedicated
// operator. R has no concatenation operator at all — paste() is a function that joins its arguments with a separator (a space by default; paste0() is the no-separator shorthand). Fortran's fixed-width fields also need trim() to strip padding, which R's dynamically-sized strings never accumulate in the first place.LEN_TRIM vs. nchar()
program length_demo
implicit none
character(len=20) :: greeting
greeting = "Hello"
print *, "Trimmed length:", len_trim(greeting)
end program length_demo greeting <- "Hello"
print(nchar(greeting)) Fortran needs
len_trim() to get the length ignoring trailing blank padding (versus len(), which returns the fixed declared width). R's nchar() always returns the exact character count with no padding to account for, since R strings carry no fixed width.Substring Slicing vs. substr()
program substring_demo
implicit none
character(len=11) :: phrase
phrase = "Hello World"
print *, phrase(1:5)
print *, phrase(7:11)
end program substring_demo phrase <- "Hello World"
print(substr(phrase, 1, 5))
print(substr(phrase, 7, 11)) Fortran's
phrase(1:5) bracket syntax and R's substr(phrase, 1, 5) function call both take a 1-based start and end position, both inclusive — a near-exact match, differing only in whether the operation is spelled with brackets or a function name.Case Conversion
program case_demo
implicit none
character(len=10) :: name
integer :: i, code
name = "alice"
do i = 1, len(name)
code = iachar(name(i:i))
if (code >= iachar('a') .and. code <= iachar('z')) then
name(i:i) = achar(code - 32)
end if
end do
print *, trim(name)
end program case_demo name <- "alice"
print(toupper(name)) Fortran has no built-in case-conversion intrinsic — converting case requires a hand-written loop over character codes with
iachar()/achar(), as shown here. R provides toupper() and tolower() directly, a case where R's standard library is considerably more convenient for a common text operation.No Fortran Equivalent — strsplit()
program manual_split_demo
implicit none
character(len=20) :: sentence
! Fortran has no built-in string-splitting intrinsic;
! splitting on a delimiter requires a hand-written loop
! using index() to locate each delimiter position.
sentence = "apple,banana,cherry"
print *, trim(sentence)
end program manual_split_demo sentence <- "apple,banana,cherry"
fruits <- strsplit(sentence, ",")[[1]]
print(fruits) Fortran has no string-splitting intrinsic at all — breaking a string on a delimiter always means writing a manual loop with
index() to find each delimiter position. R's strsplit() does the whole job in one call, returning a list (hence the [[1]] to unwrap the single result for one input string).Control Flow
IF / ELSE IF vs. if/else if
program if_demo
implicit none
integer :: score
score = 85
if (score >= 90) then
print *, "Grade: A"
else if (score >= 80) then
print *, "Grade: B"
else
print *, "Grade: C or below"
end if
end program if_demo score <- 85
if (score >= 90) {
print("Grade: A")
} else if (score >= 80) {
print("Grade: B")
} else {
print("Grade: C or below")
} Fortran's
if / else if / else / end if and R's if (...) { } else if (...) { } else { } read almost identically, with R using braces where Fortran uses then/end if to delimit each branch..AND./.OR. vs. &&/||
program logical_ops_demo
implicit none
logical :: is_weekday, is_holiday
is_weekday = .true.
is_holiday = .false.
if (is_weekday .and. .not. is_holiday) then
print *, "Office is open"
end if
end program logical_ops_demo isWeekday <- TRUE
isHoliday <- FALSE
if (isWeekday && !isHoliday) {
print("Office is open")
} Fortran surrounds its logical operators with dots (
.and., .or., .not.). R uses C-style symbols: &&, ||, !. Note R's single-&/| variants are vectorized (they operate elementwise across whole vectors), while the double forms &&/|| used in if conditions look only at the first element — a distinction that has no Fortran counterpart, since Fortran's logical operators are always scalar unless applied to whole logical arrays directly.No Fortran Equivalent — Vectorized ifelse()
program manual_where_demo
implicit none
integer :: numbers(5), labels_as_numbers(5)
integer :: i
numbers = [5, 12, 3, 20, 8]
do i = 1, 5
if (numbers(i) > 10) then
labels_as_numbers(i) = 1 ! stand-in for "big"
else
labels_as_numbers(i) = 0 ! stand-in for "small"
end if
end do
print *, labels_as_numbers
end program manual_where_demo numbers <- c(5, 12, 3, 20, 8)
labels <- ifelse(numbers > 10, "big", "small")
print(labels) R's
ifelse(condition, yesValue, noValue) evaluates a condition across an entire vector at once and picks the matching value for every element in a single expression, including non-numeric results like strings. Fortran has nothing directly equivalent for producing an arbitrary-typed result per element — the closest built-in tool, where, only assigns into an already-declared array of matching type, so a genuinely per-element branching choice like this one needs an explicit loop, as shown here.SELECT CASE vs. switch()
program select_case_demo
implicit none
character(len=10) :: color
color = "green"
select case (color)
case ("red")
print *, "Stop"
case ("green")
print *, "Go"
case default
print *, "Unknown"
end select
end program select_case_demo color <- "green"
result <- switch(color,
red = "Stop",
green = "Go",
"Unknown"
)
print(result) Fortran's
select case is a statement that runs code in each branch. R's switch() is an expression that returns a value — the trailing unnamed argument ("Unknown" here) serves as the default, matching Fortran's case default. Where Fortran executes a chosen block of statements, R's switch() is typically wrapped in an assignment to capture its result.Loops & Iteration
DO Loop vs. for Over a Vector
program do_loop_demo
implicit none
integer :: i
do i = 1, 5
print *, "Iteration:", i
end do
end program do_loop_demo for (i in 1:5) {
print(paste("Iteration:", i))
} Fortran's
do i = 1, 5 counts through a numeric range. R's for (i in 1:5) iterates directly over the elements of the vector 1:5 — conceptually R's for always iterates over a vector's elements (which happens to look like counting when that vector is a numeric range), whereas Fortran's do is a dedicated counting construct.DO WHILE vs. while
program do_while_demo
implicit none
integer :: balance
balance = 1000
do while (balance > 0)
balance = balance - 300
print *, "Balance:", balance
end do
end program do_while_demo balance <- 1000
while (balance > 0) {
balance <- balance - 300
print(paste("Balance:", balance))
} Fortran's
do while (condition) and R's while (condition) { } are functionally identical: both test the condition before each iteration and may run zero times.ELEMENTAL Functions vs. sapply()
program elemental_demo
implicit none
real :: prices(3)
prices = [10.0, 20.0, 30.0]
print *, apply_discount(prices)
contains
elemental function apply_discount(price) result(discounted)
real, intent(in) :: price
real :: discounted
discounted = price * 0.9
end function apply_discount
end program elemental_demo applyDiscount <- function(price) price * 0.9
prices <- c(10, 20, 30)
print(sapply(prices, applyDiscount)) A Fortran
elemental function is written to accept a single scalar, but the compiler automatically applies it across an entire array with no explicit loop. R's sapply() achieves the same visible effect — applying a scalar-oriented function to every element of a vector — but does so by actually looping under the hood and simplifying the results back into a vector, rather than through a compiler-level broadcasting guarantee. (In this particular case, R's ordinary vectorized prices * 0.9 would be simpler still — sapply becomes necessary once the per-element logic is too complex for a single vectorized expression.)EXIT/CYCLE vs. break/next
program exit_cycle_demo
implicit none
integer :: i
do i = 1, 10
if (mod(i, 2) == 0) cycle
if (i > 7) exit
print *, i
end do
end program exit_cycle_demo for (i in 1:10) {
if (i %% 2 == 0) next
if (i > 7) break
print(i)
} Fortran's
cycle (skip to the next iteration) and exit (break out of the loop) map onto R's next and break — same two concepts, different names for the first one.Functions
FUNCTION vs. function()
program function_demo
implicit none
print *, "Fahrenheit:", to_fahrenheit(100.0)
contains
function to_fahrenheit(celsius) result(fahrenheit)
real, intent(in) :: celsius
real :: fahrenheit
fahrenheit = celsius * 9.0 / 5.0 + 32.0
end function to_fahrenheit
end program function_demo toFahrenheit <- function(celsius) {
celsius * 9 / 5 + 32
}
print(toFahrenheit(100)) Both languages have real functions with return values usable inside expressions. Fortran requires declared types for the parameter and the result, plus an explicit
result() clause or assignment to the function's own name. R needs none of that: a function is just an anonymous value assigned to a name, its parameters carry no type at all, and the last evaluated expression in the body becomes the return value with no explicit return needed.OPTIONAL Arguments vs. Default Arguments
program optional_demo
implicit none
print *, greet("Alice")
print *, greet("Bob", "Hi")
contains
function greet(person_name, greeting_word) result(message)
character(len=*), intent(in) :: person_name
character(len=*), intent(in), optional :: greeting_word
character(len=40) :: message
character(len=10) :: actual_greeting
if (present(greeting_word)) then
actual_greeting = greeting_word
else
actual_greeting = "Hello"
end if
message = trim(actual_greeting) // ", " // trim(person_name) // "!"
end function greet
end program optional_demo greet <- function(personName, greetingWord = "Hello") {
paste0(greetingWord, ", ", personName, "!")
}
print(greet("Alice"))
print(greet("Bob", "Hi")) Fortran's
optional parameter attribute, checked with present() inside the subprogram, corresponds to R's far simpler default-argument syntax: greetingWord = "Hello" directly in the parameter list. R needs no equivalent of present() at all — the parameter is simply always bound to either the caller's value or the default.Multiple INTENT(OUT) Values vs. Returning a List
program multiple_return_demo
implicit none
real :: area, circumference
call circle_stats(5.0, area, circumference)
print *, "Area:", area
print *, "Circumference:", circumference
contains
subroutine circle_stats(radius, area_out, circ_out)
real, intent(in) :: radius
real, intent(out) :: area_out, circ_out
real, parameter :: pi = 3.14159265358979
area_out = pi * radius * radius
circ_out = 2.0 * pi * radius
end subroutine circle_stats
end program multiple_return_demo circleStats <- function(radius) {
area <- pi * radius^2
circumference <- 2 * pi * radius
list(area = area, circumference = circumference)
}
result <- circleStats(5)
print(result$area)
print(result$circumference) Fortran returns multiple values through several
intent(out) parameters on a subroutine. R has no multi-value return syntax either, but achieves the same effect more directly: bundle everything into a single named list() and return that one value, then access fields with $ on the caller side.⚠ No Closures in Fortran
program no_closure_demo
implicit none
integer :: total_calls
total_calls = 0
call increment_counter(total_calls)
call increment_counter(total_calls)
call increment_counter(total_calls)
print *, total_calls
contains
subroutine increment_counter(counter)
integer, intent(inout) :: counter
counter = counter + 1
end subroutine increment_counter
end program no_closure_demo makeCounter <- function() {
count <- 0
function() {
count <<- count + 1
count
}
}
counter <- makeCounter()
cat(counter(), counter(), counter(), "
") R functions are true closures: a nested function captures its enclosing environment, and the super-assignment operator
<<- lets it modify a variable in that captured environment even after the outer function has returned — makeCounter() hands back a working counter with private state. Fortran has no equivalent concept at all; internal subprograms after contains share the host program's variables only while that host program is still executing, and there is no way to package "a function plus its own private state" as a single returnable value.Missing Data
⚠ NA Propagates — Fortran Has No Missing-Value Concept
program no_na_demo
use ieee_arithmetic
implicit none
real :: values(3)
! Fortran has no built-in "missing value" marker.
! A sentinel value or IEEE NaN must be used and checked manually.
values = [1.0, ieee_value(1.0, ieee_quiet_nan), 3.0]
print *, "Sum:", sum(values) ! NaN poisons the whole sum
end program no_na_demo values <- c(1, NA, 3)
print(values + 1)
print(sum(values)) R's
NA is a first-class "missing" marker built into every vector, central to statistical computing where real-world data always has gaps. Any arithmetic touching an NA propagates it — sum(c(1, NA, 3)) is NA, forcing an explicit decision about how to handle missingness. Fortran has no equivalent concept at the language level; the closest approximation is IEEE NaN (accessed via ieee_value() from the ieee_arithmetic module), which similarly poisons arithmetic but carries none of R's dedicated missing-data tooling (no is.na(), no na.rm).Detecting Missing Values
program isnan_demo
use ieee_arithmetic
implicit none
real :: values(3)
values = [1.0, ieee_value(1.0, ieee_quiet_nan), 3.0]
print *, ieee_is_nan(values)
end program isnan_demo values <- c(1, NA, 3)
print(is.na(values)) Fortran's
ieee_is_nan() (from the ieee_arithmetic intrinsic module) tests each element for NaN, similarly to R's is.na(). Crucially, in both languages you cannot test for missingness with a plain equality comparison — NaN == NaN is false in Fortran, and NA == NA is itself NA in R — so the dedicated detection function is the only correct check in either language.Ignoring NA in Statistics
program manual_filter_demo
implicit none
real :: values(3), present_values(2)
integer :: present_count
values = [4.0, -1.0, 8.0] ! -1.0 stands in for a missing sentinel here
present_values = [4.0, 8.0]
present_count = 2
print *, sum(present_values) / present_count
end program manual_filter_demo values <- c(4, NA, 8)
print(mean(values, na.rm = TRUE)) Since Fortran has no first-class missing-value concept, ignoring "missing" entries in a computation means manually filtering the sentinel values out before computing a statistic. R's statistical functions accept a dedicated
na.rm = TRUE argument that skips NA entries automatically — one argument, versus the manual filtering Fortran requires.Lists
Derived Type vs. Named List
program derived_type_demo
implicit none
type :: person
character(len=20) :: name
integer :: age
end type person
type(person) :: employee
employee = person("Alice", 30)
print *, trim(employee%name)
print *, employee%age
end program derived_type_demo employee <- list(name = "Alice", age = 30)
print(employee$name)
print(employee$age) A Fortran
type is declared once with fixed field names and types, then instantiated many times — every person value has exactly the fields name and age, guaranteed by the compiler. An R list() needs no declaration at all: any list can have any fields, of any type, chosen fresh each time one is built. R offers no compile-time guarantee that two "employee" lists share the same shape.Heterogeneous Lists — No Fortran Equivalent
program homogeneous_array_demo
implicit none
! Fortran arrays must hold a single, uniform type throughout.
! Mixing a string, a number, and a boolean in one array is
! not possible without wrapping them in a variant/derived type.
integer :: numbers(3)
numbers = [1, 2, 3]
print *, numbers
end program homogeneous_array_demo mixed <- list("Alice", 30, TRUE)
print(mixed[[1]])
print(mixed[[2]])
print(mixed[[3]]) Every Fortran array holds elements of one single, uniform declared type — there is no way to mix a string, a number, and a boolean in one array without wrapping them inside a variant-like derived type first. An R
list() can freely hold values of completely different types side by side, indexed with double brackets ([[1]]) to extract a single element as its own value rather than a length-1 sub-list.Modifying and Growing a List
program allocatable_grow_demo
implicit none
integer, allocatable :: items(:)
allocate(items(2))
items = [1, 2]
items = [items, 3] ! reallocates to grow by one element
print *, items
end program allocatable_grow_demo items <- list(1, 2)
items[[3]] <- 3
print(items)
items$label <- "done" # add a NAMED field too
print(items$label) Growing a Fortran
allocatable array by one element means building a new, larger array and copying the old contents in — items = [items, 3] does this implicitly but still reallocates under the hood. Growing an R list is a single assignment past its current length (items[[3]] <- 3), and unlike a Fortran array, an R list can also gain entirely new named fields on the fly (items$label <- "done") — something no Fortran array or even a fixed type definition permits.Objects (S3)
Derived Type vs. S3 Class
program type_no_methods_demo
implicit none
type :: dog
character(len=20) :: name
end type dog
type(dog) :: rex
rex = dog("Rex")
print *, trim(rex%name)
end program type_no_methods_demo dog <- list(name = "Rex")
class(dog) <- "Dog"
print(class(dog)) A Fortran
type is a fixed, compiler-checked structural definition. R's lightest object system, S3, is the opposite extreme: any ordinary value (here a plain list) becomes an "object" simply by attaching a class attribute to it — no declaration, no structural guarantee, just a label that later drives method dispatch.No Dynamic Dispatch in Fortran vs. S3 Generics
program no_dispatch_demo
implicit none
type :: dog
character(len=20) :: name
end type dog
type(dog) :: rex
rex = dog("Rex")
print *, trim(speak(rex))
contains
function speak(animal) result(sound)
type(dog), intent(in) :: animal
character(len=40) :: sound
sound = trim(animal%name) // " says Woof"
end function speak
end program no_dispatch_demo speak <- function(x) UseMethod("speak")
speak.Dog <- function(x) paste(x$name, "says Woof")
rex <- structure(list(name = "Rex"), class = "Dog")
print(speak(rex)) The Fortran function
speak here only ever handles one specific type (dog) — calling it with anything else is a compile-time type error, and there is no built-in mechanism for the same function name to behave differently depending on the runtime type of its argument. R's S3 system provides exactly that: speak <- function(x) UseMethod("speak") declares a generic, and R dispatches to speak.Dog based on the class attribute of whatever value is passed in at runtime — a form of polymorphism Fortran's static type system does not provide.No Fortran Equivalent — Customizing print()
program manual_print_demo
implicit none
type :: point
real :: x, y
end type point
type(point) :: origin
origin = point(3.0, 4.0)
! Fortran has no customizable "print this type" hook —
! every call site must format the fields manually.
print *, "Point(", origin%x, ",", origin%y, ")"
end program manual_print_demo newPoint <- function(x, y) {
structure(list(x = x, y = y), class = "Point")
}
print.Point <- function(x, ...) {
cat("Point(", x$x, ",", x$y, ")
")
}
print(newPoint(3, 4)) Fortran has no hook for customizing how a value displays — every piece of code that prints a
point must format its fields by hand, every time. R's print() is itself an S3 generic, so defining print.Point once makes every future print(somePoint) automatically use that formatting, site-wide.Error Handling
⚠ No Exceptions in Fortran vs. stop()
program status_flag_demo
implicit none
integer :: amount, status_flag
call withdraw(-5, amount, status_flag)
if (status_flag /= 0) then
print *, "Error: amount must be positive"
else
print *, amount
end if
! A Fortran program that truly cannot continue would instead call
! "stop 1" here — an unconditional, uncatchable program termination
! with exit code 1, not a recoverable condition like R's stop().
contains
subroutine withdraw(requested_amount, amount_out, status_flag_out)
integer, intent(in) :: requested_amount
integer, intent(out) :: amount_out, status_flag_out
if (requested_amount < 0) then
status_flag_out = 1
amount_out = 0
else
status_flag_out = 0
amount_out = requested_amount
end if
end subroutine withdraw
end program status_flag_demo withdraw <- function(amount) {
if (amount < 0) stop("amount must be positive")
amount
}
result <- tryCatch(
withdraw(-5),
error = function(e) paste("caught:", conditionMessage(e))
)
print(result) Fortran has no exception-handling mechanism at all: an error condition is normally communicated through a status integer that the caller must check explicitly, as shown here — or, for a truly unrecoverable situation, by calling
stop to terminate the entire program immediately with a given exit code, with no way to "catch" it and keep running. R's stop() looks similar by name but behaves completely differently: it raises a catchable condition that tryCatch() can intercept, recover from, and continue execution afterward — Fortran has nothing resembling this recoverable middle ground between "check a flag" and "kill the whole program."tryCatch() Has No Fortran Equivalent
program status_code_demo
implicit none
print *, safe_divide(10.0, 2.0)
print *, safe_divide(10.0, 0.0)
contains
function safe_divide(numerator, denominator) result(quotient)
real, intent(in) :: numerator, denominator
real :: quotient
if (denominator == 0.0) then
quotient = -1.0 ! sentinel value standing in for "error"
else
quotient = numerator / denominator
end if
end function safe_divide
end program status_code_demo safeDivide <- function(a, b) {
tryCatch(
if (b == 0) stop("divide by zero") else a / b,
error = function(e) NA
)
}
print(safeDivide(10, 2))
print(safeDivide(10, 0)) Since Fortran has no exception mechanism, "handling an error" always means checking a status flag or a sentinel return value after the fact — the caller must know in advance to check it. R's
tryCatch() wraps an expression and supplies a handler keyed by condition class (error = function(e) ...) that runs automatically if that expression raises a matching condition, with no sentinel-value convention required.Warnings
program manual_warning_demo
implicit none
real :: value
value = -5.0
if (value < 0.0) then
print *, "Warning: negative input"
end if
print *, abs(value)
end program manual_warning_demo check <- function(value) {
if (value < 0) warning("negative input")
abs(value)
}
result <- tryCatch(
check(-5),
warning = function(w) paste("warned:", conditionMessage(w))
)
print(result) Fortran has no distinct "warning" mechanism separate from ordinary printed output — a program that wants to warn without stopping simply prints a message and continues, with nothing structured for a caller to detect. R's
warning() raises a genuine, catchable condition distinct from an error: it does not abort execution by default, but tryCatch() can still intercept it with a warning = handler if the caller wants to react to it specifically.⚠ Gotchas for Fortran Programmers
⚠ You Cannot Test NA with ==
program nan_equality_demo
use ieee_arithmetic
implicit none
real :: value
value = ieee_value(1.0, ieee_quiet_nan)
print *, value == value ! prints F — NaN never equals itself either
end program nan_equality_demo value <- NA
print(value == NA) # prints NA, not TRUE or FALSE — a common trap
print(is.na(value)) # the only correct check Fortran programmers already know IEEE
NaN famously never equals itself (NaN == NaN is false), so the general caution around comparing special values transfers. R's trap is subtly different and easier to fall into: value == NA does not return FALSE — it returns NA itself, since any comparison involving a missing value is itself unknown. Code that does if (value == NA) silently misbehaves (an NA condition in if raises an error) rather than cleanly failing the comparison; is.na() is the only correct way to test for missingness.⚠ Recycling Warns Only Sometimes
program shape_mismatch_would_error_demo
implicit none
integer :: a(6), b(6)
a = [1, 2, 3, 4, 5, 6]
! A genuine shape mismatch here would be a hard compile-time
! or runtime error in Fortran — never silent.
b = [10, 20, 10, 20, 10, 20]
print *, a + b
end program shape_mismatch_would_error_demo a <- c(1, 2, 3, 4, 5, 6)
b <- c(10, 20) # length 2 divides evenly into 6 — no warning at all
print(a + b)
c <- c(10, 20, 30) # length 3 also divides evenly — still no warning
print(a + c)
d <- c(10, 20, 30, 40) # length 4 does NOT divide evenly into 6 — warns
print(a + d) R only warns about recycling when the longer vector's length is not an exact multiple of the shorter one's — recycling by a length that divides evenly (as with lengths 2 or 3 into 6 above) produces no warning message at all, even though it is exactly the same silent-repetition behavior. A Fortran programmer relying on "R will warn me if something's off" should know that the warning is a partial safety net at best, not a guarantee — the only way to be certain no recycling occurred is to check vector lengths explicitly before the operation.
⚠ NULL and NA Are Not the Same Thing
program no_null_concept_demo
implicit none
! Fortran has no direct equivalent of either NULL or NA as a
! value that can occupy a numeric variable's slot — the closest
! related concept is a NULL pointer, which is a different idea
! entirely (an association state, not a data value).
integer, pointer :: maybe_value
maybe_value => null()
print *, associated(maybe_value)
end program no_null_concept_demo emptyValue <- NULL
missingValue <- NA
print(is.null(emptyValue))
print(is.na(missingValue))
print(length(emptyValue)) # 0 — NULL has no length
print(length(missingValue)) # 1 — NA is a real length-1 value R has two different ways for "nothing" to appear, and they are not interchangeable:
NULL represents the complete absence of a value (it has length 0 and cannot occupy a slot in a vector), while NA represents one specific, known-missing value inside a vector (it has length 1, like any other element). Fortran's closest concept, a null() pointer, is neither of these — it describes a pointer's association state, not a placeholder value that arithmetic or a vector can hold.⚠ Errors Surface at Runtime, Not Compile Time
program compile_time_catch_demo
implicit none
integer :: count
count = 5
! print *, count + "hello" ! caught by the compiler — never compiles
print *, count
end program compile_time_catch_demo count <- 5
print(count)
# The next line only fails when it actually RUNS, not before —
# caught here with tryCatch so the script keeps going and reports it:
result <- tryCatch(count + "hello", error = function(e) conditionMessage(e))
print(result) Fortran's compiler rejects a type mismatch like adding a number and a string before the program ever runs. R performs no such check ahead of time — the equivalent mistake compiles (there is no compile step to reject it) and only produces an error the moment that specific line executes, which could be deep inside a rarely-triggered branch that a test run never reaches. Every type error a Fortran compiler would have caught for free becomes, in R, a bug that only surfaces in production.