PONY λ M2 Modula-2

Fortran.CodeCompared.To/Ruby

An interactive executable cheatsheet comparing Fortran and Ruby

Fortran 2018 (GCC 15.2) Ruby 4.0
Hello World & Running
Hello, World
program hello implicit none print *, "Hello, World!" end program hello
puts "Hello, World!"
Ruby has no program/end program wrapper and nothing to compile: a script is a plain sequence of statements executed top to bottom. puts writes a line to standard output — the list-directed print *, of Ruby, but with no leading space.
Comments
program comments implicit none ! a full-line comment print *, "done" ! a trailing comment end program comments
# a full-line comment puts "done" # a trailing comment
Ruby marks a comment with # rather than Fortran’s !, and it runs to the end of the line the same way. There is no block-comment form in everyday use; Rubyists prefix each line with #.
No IMPLICIT NONE
program typing implicit none integer :: count count = 5 print *, count end program typing
# No 'implicit none', no declarations at all. count = 5 puts count
Ruby has no implicit-typing rule to defend against, so there is no implicit none and no type-and-kind declaration line. A variable springs into existence on first assignment and simply refers to whatever object you gave it.
Formatted output
program formatted implicit none real :: pi pi = 3.14159 write(*, '(A, F6.3)') "pi = ", pi end program formatted
pi = 3.14159 printf("pi = %6.3f\n", pi) puts format("pi = %6.3f", pi)
Ruby keeps C-style format strings through printf and format, so the F6.3 edit descriptor becomes %6.3f. The format lives inline rather than in a separate FORMAT statement or a parenthesized descriptor string.
Variables & Types
Dynamic typing
program declare implicit none integer :: quantity real :: price quantity = 3 price = 9.99 print *, quantity, price end program declare
quantity = 3 # an Integer price = 9.99 # a Float p quantity, price p quantity.class # Integer p price.class # Float
Ruby infers everything at run time — there is no integer :: / real :: declaration section. The type belongs to the object, not the variable, so you can ask any value its class. A type mismatch surfaces only when an operation actually runs.
Everything is an object
program intrinsic implicit none integer :: value value = -5 ! Intrinsics operate on the value from outside. print *, abs(value) print *, mod(value, 3) end program intrinsic
value = -5 # Operations are methods sent to the object. p value.abs # 5 p value % 3 # remainder p value.class # Integer p 5.times.to_a # [0, 1, 2, 3, 4]
In Ruby even an integer is an object, so operations are methods you send to it (value.abs) rather than intrinsic functions applied from outside (abs(value)). There are no unboxed primitives and no distinction between an INTEGER and a "boxed" integer.
Variables are untyped
program retype implicit none ! A name is bound to one type for the whole scope. integer :: item item = 42 print *, item end program retype
item = 42 # holds an Integer puts item item = "forty-two" # now holds a String — legal puts item
A Ruby variable is just a label with no fixed type, so reassigning it to a value of a different class is ordinary and legal. In Fortran a name’s type is fixed by its declaration for the entire scope; there is no equivalent freedom.
Constants
program constants implicit none real, parameter :: gravity = 9.81 print *, gravity end program constants
GRAVITY = 9.81 # a constant: name begins with a capital puts GRAVITY # Reassigning only warns; freeze to make it truly fixed. SPEED_LIMIT = 100 puts SPEED_LIMIT
Ruby has no parameter attribute; instead any name beginning with a capital letter is a constant. Reassigning one produces a warning rather than an error, so the convention is enforced socially — much weaker than Fortran’s compile-time parameter.
Numbers
INTEGER/REAL → Integer/Float
program numeric implicit none integer :: whole real :: fraction whole = 7 fraction = 3.5 print *, whole, fraction end program numeric
whole = 7 # Integer fraction = 3.5 # Float (a double) p whole p fraction p whole.to_f # explicit conversion, like REAL(whole)
Ruby’s Integer and Float stand in for INTEGER and REAL, but a Float is always double precision — there is no single/double split and no KIND to specify. Convert explicitly with to_f/to_i rather than REAL()/INT().
Arbitrary-precision integers
program overflow implicit none integer(kind=8) :: big ! Even a 64-bit INTEGER overflows past ~9.2e18. big = 2_8 ** 62 print *, big end program overflow
big = 2 ** 100 # no overflow, ever puts big puts (10 ** 50) + 1 # exact, arbitrary precision
A Ruby Integer grows to whatever size the value needs, transparently — there is no fixed width, no KIND=8, and no overflow. This trades Fortran’s predictable machine-word performance for never having to reason about integer range.
Integer division
program division implicit none print *, 7 / 2 ! 3 — integer division print *, real(7) / 2 ! 3.5 — one REAL promotes end program division
p 7 / 2 # 3 — Integer / Integer truncates p 7.0 / 2 # 3.5 — one Float promotes the result p 7.fdiv(2) # 3.5 — explicit float division p 7 % 2 # 1 — remainder, like mod(7, 2)
The rule matches Fortran: integer divided by integer truncates, and a single Float operand promotes the whole expression to Float. Ruby decides by the runtime classes of the operands, the way Fortran decides by declared types.
Complex numbers
program complex_demo implicit none complex :: z z = (1.0, 2.0) print *, z print *, abs(z) ! magnitude end program complex_demo
z = Complex(1, 2) # or 1 + 2i p z p z.abs # magnitude p z.real # 1 p z.imaginary # 2
Ruby has a built-in Complex class, so complex arithmetic works out of the box as it does in Fortran — but the imaginary literal is 2i and the parts are read with the methods real and imaginary rather than the intrinsics REAL()/AIMAG().
Math functions
program math_demo implicit none print *, sqrt(2.0) print *, sin(3.14159 / 2) print *, max(3, 8) end program math_demo
p Math.sqrt(2.0) p Math.sin(Math::PI / 2) p [3, 8].max # or 3 and 8 compared p 2 ** 10 # ** is exponentiation, like **
Transcendental functions live in the Math module (Math.sqrt, Math::PI) rather than being global intrinsics. Note max is a method on an array of values or on the numbers themselves, not a free function taking a list.
Strings
Dynamic-length strings
program strings implicit none character(len=20) :: name name = "Ada" ! Fixed width: 'name' is padded to 20 chars. print *, "[", trim(name), "]" end program strings
name = "Ada" # length is exactly 3, grows as needed puts "[#{name}]" p name.length # 3 — no padding, no fixed width longer = name + " Lovelace" puts longer
A Ruby String has no declared length — it is a dynamic, resizable object, so there is no fixed character(len=20) width and no trailing blanks to trim. Its length is simply however many characters it currently holds.
Interpolation vs //
program concat implicit none character(len=:), allocatable :: greeting character(len=20) :: name name = "Ada" greeting = "Hello, " // trim(name) print *, greeting end program concat
name = "Ada" greeting = "Hello, #{name}" # interpolation puts greeting also = "Hello, " + name # + concatenates, like // puts also
Ruby’s #{expression} interpolates any value directly into a string literal, evaluating its to_s — there is no concatenation operator needed for the common case. When you do want to join strings, + plays the role of Fortran’s //.
String operations
program strops implicit none character(len=20) :: text text = " hello " print *, len_trim(text) print *, adjustl(text) ! No built-in uppercase intrinsic in standard Fortran. end program strops
text = " hello " p text.strip # "hello" — like trim + adjustl p text.strip.upcase # "HELLO" p text.strip.length # 5 p "hello".reverse # "olleh"
String manipulation is a rich set of methods on the object: strip removes surrounding whitespace (both adjustl and trim in one), and upcase/reverse do what standard Fortran has no intrinsic for. They chain because each returns a new string.
Substrings
program substr implicit none character(len=11) :: phrase phrase = "hello world" ! 1-based, inclusive range. print *, phrase(1:5) print *, phrase(7:11) end program substr
phrase = "hello world" p phrase[0..4] # "hello" — 0-based, inclusive range p phrase[6..] # "world" — open-ended range p phrase[0, 5] # "hello" — start + length p phrase[-5..] # "world" — negative index from the end
The big adjustment: Ruby strings are 0-based, so Fortran’s phrase(1:5) becomes phrase[0..4]. Ruby also allows negative indices counting from the end and open-ended ranges — neither of which Fortran substring notation offers.
Arrays
1-based → 0-based indexing
program indexing implicit none integer :: numbers(3) numbers = [10, 20, 30] ! Fortran arrays start at index 1 by default. print *, numbers(1) ! 10 — the first element print *, numbers(3) ! 30 — the last element end program indexing
numbers = [10, 20, 30] # Ruby arrays start at index 0. p numbers[0] # 10 — the first element p numbers[2] # 30 — the last element p numbers[-1] # 30 — negative index from the end p numbers.first # 10 — or just ask by name
This is the single most important adjustment: Ruby arrays are 0-based, so the first element is numbers[0], not numbers(1). There is no way to declare a custom lower bound as Fortran’s numbers(0:2) allows; 0 is always the first index.
Array construction
program construct implicit none integer :: primes(4) primes = [2, 3, 5, 7] print *, primes print *, size(primes) end program construct
primes = [2, 3, 5, 7] p primes p primes.size # 4 — like size(primes) # Arrays are heterogeneous and can hold any object: mixed = [1, "two", 3.0, :four] p mixed
The [ ] literal looks like a Fortran array constructor, but a Ruby Array is a dynamic, heterogeneous object — it can hold values of different classes at once, which a Fortran array (uniform type and kind) cannot.
Growing arrays
program allocate_demo implicit none integer, allocatable :: stack(:) allocate(stack(0)) ! Growing means reallocating the whole array. stack = [stack, 1] stack = [stack, 2] print *, stack end program allocate_demo
stack = [] # no allocate, no fixed size stack.push(1) # grows in place stack << 2 # << is the same as push p stack # [1, 2] top = stack.pop # removes and returns 2 p top
A Ruby array grows and shrinks on demand with push/<</pop — no allocatable attribute, no allocate, and no reallocation dance. It is a stack, queue, and vector all in one object.
Array slicing
program slicing implicit none integer :: values(5) values = [10, 20, 30, 40, 50] print *, values(2:4) ! elements 2,3,4 print *, values(1:5:2) ! stride of 2 end program slicing
values = [10, 20, 30, 40, 50] p values[1..3] # [20, 30, 40] — 0-based range p values.values_at(0, 2, 4) # [10, 30, 50] — a stride by hand p values.each_slice(2).to_a # [[10,20],[30,40],[50]]
Ruby slices with a Range (values[1..3]), 0-based and inclusive. There is no built-in strided slice like Fortran’s values(1:5:2); you express a stride with values_at, step, or each_slice.
Array Operations
Elementwise operations
program elementwise implicit none integer :: a(3), b(3), c(3) a = [1, 2, 3] b = [10, 20, 30] ! Whole-array arithmetic is built in. c = a + b print *, c end program elementwise
a = [1, 2, 3] b = [10, 20, 30] # No whole-array arithmetic; pair up with zip, then map. c = a.zip(b).map { |x, y| x + y } p c # [11, 22, 33] doubled = a.map { |n| n * 2 } p doubled # [2, 4, 6]
Ruby has no whole-array arithmetic — a + b concatenates two arrays rather than adding elementwise. You express Fortran’s c = a + b by zip-ing the arrays into pairs and map-ing over them. (For heavy numerics, the numo-narray gem restores Fortran-style vectorized operations.)
Reductions
program reductions implicit none integer :: data(5) data = [3, 1, 4, 1, 5] print *, sum(data) print *, product(data) print *, maxval(data) print *, minval(data) end program reductions
data = [3, 1, 4, 1, 5] p data.sum # like sum(data) p data.reduce(:*) # like product(data) p data.max # like maxval(data) p data.min # like minval(data)
The Fortran reduction intrinsics map to Enumerable methods: sum, max, and min read the same, and product becomes reduce(:*), which folds the multiplication operator over the array.
WHERE → select/map
program masking implicit none integer :: values(5), result(5) values = [-2, 3, -4, 5, -6] result = values ! Masked assignment: only where the condition holds. where (values < 0) result = 0 print *, result end program masking
values = [-2, 3, -4, 5, -6] # Transform conditionally with map: result = values.map { |n| n < 0 ? 0 : n } p result # [0, 3, 0, 5, 0] # Or keep only the matching elements: positives = values.select { |n| n > 0 } p positives # [3, 5]
Fortran’s where masked assignment becomes a map with a conditional expression, and Fortran’s pack/masking becomes select (keep matching) or reject (drop matching). Each returns a new array rather than assigning into an existing one.
Matrix multiply
program matrix implicit none integer :: a(2,2), b(2,2), c(2,2) a = reshape([1, 2, 3, 4], [2, 2]) b = reshape([5, 6, 7, 8], [2, 2]) c = matmul(a, b) print *, c end program matrix
require "matrix" a = Matrix[[1, 2], [3, 4]] b = Matrix[[5, 6], [7, 8]] c = a * b # matrix multiply, like matmul p c.to_a
Ruby has no built-in multidimensional array; a nested array is just an array of arrays with no elementwise or matmul semantics. The standard-library Matrix class supplies real linear algebra, where * is matrix multiplication — but there is nothing as fundamental as Fortran’s native rank-2 arrays and reshape.
Control Flow
IF / ELSE
program branch implicit none integer :: score score = 72 if (score >= 90) then print *, "A" else if (score >= 60) then print *, "pass" else print *, "fail" end if end program branch
score = 72 if score >= 90 puts "A" elsif score >= 60 puts "pass" else puts "fail" end
The structure matches, with cosmetic differences: no then keyword, no parentheses required around the condition, elsif (one word) for else if, and a single end instead of end if. An if in Ruby is also an expression that returns a value.
SELECT CASE
program choose implicit none integer :: code code = 2 select case (code) case (1) print *, "one" case (2, 3) print *, "two or three" case default print *, "other" end select end program choose
code = 2 case code when 1 puts "one" when 2, 3 puts "two or three" else puts "other" end
Ruby’s case/when mirrors select case, including multiple values per branch (when 2, 3). It is more powerful, though: a when can match a range (when 1..10), a class, or a regular expression, because it tests with the === operator.
Logical operators
program logic implicit none logical :: ready, willing ready = .true. willing = .false. if (ready .and. .not. willing) then print *, "ready but not willing" end if end program logic
ready = true willing = false if ready && !willing puts "ready but not willing" end p (ready || willing) # true
Ruby uses the C-style &&, ||, and ! in place of .and., .or., and .not., and the literals are the bare words true/false without the surrounding dots. Only false and nil are falsy; every other object, including 0, is truthy.
Loops & Iteration
DO loop → each/times
program counting implicit none integer :: index do index = 1, 5 print *, index end do end program counting
# Idiomatic Ruby iterates a range, not an index counter: (1..5).each do |index| puts index end # Or, when you just need N repetitions: 5.times { |index| puts index } # index runs 0..4
The counting do loop becomes iteration over a Range with each, or 5.times when the counter itself does not matter. Ruby rarely manages a bare loop index — you ask a collection to hand you each element instead.
Stepped & reverse loops
program stepping implicit none integer :: index do index = 10, 1, -2 print *, index end do end program stepping
10.step(1, -2) do |index| puts index end # ascending stride: (0..10).step(2) { |index| puts index }
Fortran’s three-part do index = start, stop, step becomes start.step(stop, step) or range.step(size). Both directions work; a negative step counts down, exactly as in Fortran.
DO WHILE
program repeat implicit none integer :: total total = 0 do while (total < 10) total = total + 3 end do print *, total end program repeat
total = 0 while total < 10 total += 3 # += works; there is no ++ operator end puts total # 12
Ruby’s while drops the do keyword and the parentheses. Note there is no ++; use total += 3. Ruby also offers until (loop while the condition is false), the inverse form Fortran lacks.
Iterating an array
program iterate implicit none integer :: values(3), index values = [10, 20, 30] do index = 1, size(values) print *, index, values(index) end do end program iterate
values = [10, 20, 30] # Iterate elements directly — no index needed: values.each { |value| puts value } # Need the index too? each_with_index gives both: values.each_with_index do |value, index| puts "#{index}: #{value}" end
Rather than loop an index from 1 to size and subscript the array, Ruby hands you each element directly with each. When you genuinely need the position, each_with_index yields both — and the index is 0-based.
Procedures & Functions
SUBROUTINE → method
program run implicit none call greet("Ada") contains subroutine greet(name) character(len=*), intent(in) :: name print *, "Hello, ", trim(name) end subroutine greet end program run
def greet(name) puts "Hello, #{name}" end greet("Ada") # no 'call' keyword
A subroutine — a procedure with no return value — becomes an ordinary def, invoked without a call keyword. There is no contains section or separate declaration of the dummy argument’s type; the parameter is just a name.
FUNCTION → return value
program compute implicit none print *, square(5) contains integer function square(x) integer, intent(in) :: x square = x * x end function square end program compute
def square(x) x * x # last expression is the return value end p square(5) # 25
A Ruby method returns its last evaluated expression implicitly — there is no result variable to assign (as Fortran assigns to the function name) and an explicit return is rarely written. The return type is not declared; it is whatever object the body produces.
INTENT & argument passing
program modify implicit none integer :: value value = 10 call double_it(value) ! passed by reference print *, value ! 20 — the caller's copy changed contains subroutine double_it(x) integer, intent(inout) :: x x = x * 2 end subroutine double_it end program modify
def double_it(number) number * 2 # returns a new value; caller must capture it end value = 10 value = double_it(value) # reassign to "modify" p value # 20
Ruby has no intent(inout) and no pass-by-reference for a rebind: an integer argument cannot be changed in the caller’s scope. You return the new value and reassign. (Mutable objects like arrays can be changed in place, since the reference is shared — but the variable binding itself never is.)
OPTIONAL → default arguments
program greeting implicit none call welcome() call welcome("Ada") contains subroutine welcome(name) character(len=*), intent(in), optional :: name if (present(name)) then print *, "Hello, ", trim(name) else print *, "Hello, guest" end if end subroutine welcome end program greeting
def welcome(name = "guest") puts "Hello, #{name}" end welcome # "Hello, guest" welcome("Ada") # "Hello, Ada"
Ruby gives a parameter a default value directly in the signature, so there is no optional attribute and no present() check — an omitted argument simply takes its default. This is terser than Fortran’s optional-plus-present idiom.
Recursion
program fact implicit none print *, factorial(5) contains recursive integer function factorial(n) result(value) integer, intent(in) :: n if (n <= 1) then value = 1 else value = n * factorial(n - 1) end if end function factorial end program fact
def factorial(n) n <= 1 ? 1 : n * factorial(n - 1) end p factorial(5) # 120
Ruby methods are recursive by default — there is no recursive keyword to opt in, and no result variable. The ternary condition ? a : b keeps the whole body to one expression here.
Derived Types → Classes
TYPE → class
program point_demo implicit none type :: point real :: x, y end type point type(point) :: origin origin%x = 3.0 origin%y = 4.0 print *, origin%x, origin%y end program point_demo
Point = Struct.new(:x, :y) # or: Data.define(:x, :y) origin = Point.new(3.0, 4.0) p origin.x # 3.0 — a method, not %-field access p origin.y origin.x = 5.0 # Struct fields are mutable p origin.x
A Fortran type becomes a Ruby class. Struct.new generates one with accessor methods in a single line — fields are read as origin.x (a method call), not origin%x. Use Data.define instead when you want an immutable value object.
Type-bound procedures
module geometry implicit none type :: circle real :: radius contains procedure :: area end type circle contains real function area(self) class(circle), intent(in) :: self area = 3.14159 * self%radius ** 2 end function area end module geometry program main use geometry implicit none type(circle) :: c c%radius = 2.0 print *, c%area() end program main
class Circle def initialize(radius) @radius = radius # an instance variable end def area 3.14159 * @radius ** 2 end end circle = Circle.new(2.0) p circle.area
Where Fortran attaches a type-bound procedure and passes self explicitly, a Ruby class defines methods that reach instance state through @radius — no explicit self parameter and no separate contains block. The constructor is the special method initialize.
Type extension → inheritance
module animals implicit none type :: animal character(len=20) :: name end type animal type, extends(animal) :: dog end type dog end module animals program main use animals implicit none type(dog) :: rex rex%name = "Rex" print *, trim(rex%name) end program main
class Animal attr_reader :name def initialize(name) @name = name end def describe = "a nameless animal named #{name}" end class Dog < Animal # < means "inherits from" def describe = "a dog named #{name}" end puts Dog.new("Rex").describe
Ruby inheritance uses < where Fortran uses extends. Beyond inheriting fields, a Ruby subclass can override methods and call super — full polymorphism, versus Fortran’s type extension which mainly adds components and type-bound procedures.
Modules & Namespacing
MODULE → module/class
module constants implicit none real, parameter :: pi = 3.14159 contains real function circle_area(radius) real, intent(in) :: radius circle_area = pi * radius ** 2 end function circle_area end module constants program main use constants implicit none print *, circle_area(2.0) end program main
module Geometry PI = 3.14159 def self.circle_area(radius) PI * radius ** 2 end end puts Geometry.circle_area(2.0) # namespaced call
A Ruby module groups constants and methods under a namespace, like a Fortran module. A def self.name method is called on the module itself (Geometry.circle_area) — the counterpart of a module procedure made available through use.
USE → require & include
module utilities implicit none contains function doubled(x) result(y) integer, intent(in) :: x integer :: y y = x * 2 end function doubled end module utilities program main use utilities ! bring names into scope implicit none print *, doubled(21) end program main
module Doubler def doubled(x) = x * 2 # instance method for includers end class Calculator include Doubler # mixes the module's methods in end p Calculator.new.doubled(21) # 42
Fortran’s use brings a module’s public names into scope. Ruby splits this: require loads a file, while include mixes a module’s methods into a class — a form of code reuse (a mixin) that Fortran has no direct equivalent for, sitting between a module and inheritance.
Hashes
A key–value store
program lookup implicit none ! Fortran has no dictionary type. The usual workaround ! is parallel arrays searched by hand. character(len=5) :: names(2) integer :: ages(2) names = ["Ada ", "Alan "] ages = [36, 41] print *, trim(names(1)), ages(1) end program lookup
ages = { "Ada" => 36, "Alan" => 41 } p ages["Ada"] # 36 — O(1) lookup by key p ages.size # 2 ages["Grace"] = 45 # insert p ages.keys # ["Ada", "Alan", "Grace"]
This is a capability Fortran simply lacks: the Hash is a built-in, growable map from keys to values with O(1) lookup. Where Fortran forces parallel arrays and a linear search, Ruby indexes by any object as a key — no fixed size, no manual scanning.
Iterating & counting
program tally implicit none ! Counting occurrences in Fortran means a manual loop ! over parallel key/count arrays. integer :: counts(3) character :: letters(3) letters = ["a", "b", "c"] counts = [0, 0, 0] counts(1) = counts(1) + 1 print *, letters(1), counts(1) end program tally
# A default of 0 makes frequency counting a one-liner: tally = Hash.new(0) "banana".each_char { |letter| tally[letter] += 1 } p tally # {"b"=>1, "a"=>3, "n"=>2} tally.each do |letter, count| puts "#{letter}: #{count}" end
A Hash.new(0) returns 0 for any absent key, turning frequency counting into a single line — no pre-sized arrays, no search. Iterating yields each key–value pair to the block, which destructures it into two parameters.
Blocks & Closures
Blocks: map & select
program transform implicit none integer :: numbers(4), squares(4) integer :: index numbers = [1, 2, 3, 4] ! Transform with an explicit loop. do index = 1, size(numbers) squares(index) = numbers(index) ** 2 end do print *, squares end program transform
numbers = [1, 2, 3, 4] # A block passed to map transforms each element: squares = numbers.map { |n| n ** 2 } p squares # [1, 4, 9, 16] evens = numbers.select { |n| n.even? } p evens # [2, 4]
A block — an anonymous chunk of code in { } — is Ruby’s central idiom and has no Fortran equivalent. map applies it to each element and collects the results, replacing the explicit index loop and temporary result array.
Folding with reduce
program accumulate implicit none integer :: data(5), total, index data = [1, 2, 3, 4, 5] total = 0 do index = 1, size(data) total = total + data(index) end do print *, total end program accumulate
data = [1, 2, 3, 4, 5] # reduce folds the array down to a single value: total = data.reduce(0) { |sum, n| sum + n } p total # 15 # For a plain sum, the built-in is enough: p data.sum # 15
A running accumulation over a loop is captured by reduce (also called inject): it threads an accumulator through the block for each element and returns the final value — the general form behind sum, product, and the rest.
Procedures as values
program proc_ptr implicit none procedure(op), pointer :: operation operation => add print *, operation(3, 4) contains integer function add(a, b) integer, intent(in) :: a, b add = a + b end function add integer function op(a, b) integer, intent(in) :: a, b op = 0 end function op end program proc_ptr
add = ->(a, b) { a + b } # a lambda, stored in a variable p add.call(3, 4) # 7 p add.(3, 4) # 7 — shorthand # Pass one straight into an iterator: p [1, 2, 3].map { |n| add.call(n, 10) } # [11, 12, 13]
A Ruby lambda (->(a, b) { … }) is a first-class function value you store and pass around — much simpler than a Fortran procedure pointer with its matching abstract interface. You invoke it with call (or the .() shorthand).
Error Handling
STOP → raise & exceptions
program guard implicit none integer :: denominator denominator = 0 if (denominator == 0) then ! stop halts the whole program — there is no recovery. stop "cannot divide by zero" end if print *, 10 / denominator end program guard
def safe_divide(a, b) raise ZeroDivisionError, "cannot divide by zero" if b.zero? a / b end begin puts safe_divide(10, 0) rescue ZeroDivisionError => error puts "caught: #{error.message}" end
Ruby has true exceptions: raise throws one and begin/rescue catches it, unwinding the stack until a handler is found. Fortran’s only comparable tool is stop/error stop, which halts the program outright — there is no catching or recovery.
Status flags → begin/rescue
File I/O is unavailable in the in-browser runtime, so the Fortran example is shown for reference only.
program readfile implicit none integer :: status ! Fortran surfaces errors through an integer status code. open(unit=10, file="missing.txt", status="old", iostat=status) if (status /= 0) then print *, "open failed, code", status else close(10) end if end program readfile
def parse_number(text) Integer(text) # raises on bad input rescue ArgumentError => error puts "parse failed: #{error.message}" nil # a sentinel, if you want one end p parse_number("42") # 42 p parse_number("oops") # nil
Where Fortran threads an iostat integer through every fallible call and checks it by hand, Ruby lets the failure raise and handles it in one rescue. A method body is an implicit begin, so the rescue can sit at method level with no explicit block.
Guaranteed cleanup
program cleanup implicit none ! Fortran has no finally; you close resources on every ! path by hand, including before each error stop. print *, "acquire" print *, "use" print *, "release" end program cleanup
def with_resource puts "acquire" yield ensure puts "release" # runs on normal exit AND on exception end with_resource { puts "use" }
The ensure clause runs whether the body returns normally or raises, guaranteeing cleanup in one place. Fortran has no such construct — you repeat the close/deallocate on every exit path, which is exactly the bookkeeping ensure removes.
I/O & Files
print/write → puts/p
program output implicit none integer :: values(3) values = [1, 2, 3] print *, "list:", values write(*, *) "again:", values end program output
values = [1, 2, 3] puts "list: #{values.join(', ')}" # human-facing p values # developer-facing: [1, 2, 3] print "no newline" # print omits the newline puts
Ruby splits output by audience: puts writes a value’s to_s plus a newline, p writes the inspect form (with brackets and quotes) for debugging, and print writes to_s with no newline. There is no * list-directed format to remember.
Reading input
Standard input is unavailable in the in-browser runtime, so this example is shown for reference only.
program input implicit none character(len=100) :: name print *, "What is your name?" read(*, '(A)') name print *, "Hello, ", trim(name) end program input
puts "What is your name?" name = gets.chomp # read a line, strip the newline puts "Hello, #{name}"
gets reads one line from standard input as a string; chomp removes the trailing newline. It returns nil at end of input rather than raising, and there is no format descriptor or fixed-width buffer to declare.
Reading a file
File I/O is unavailable in the in-browser runtime, so this example is shown for reference only.
program readlines implicit none integer :: unit, status character(len=100) :: line open(newunit=unit, file="notes.txt", status="old") do read(unit, '(A)', iostat=status) line if (status /= 0) exit print *, trim(line) end do close(unit) end program readlines
contents = File.read("notes.txt") contents.each_line do |line| puts line.chomp end # whole file as an array of lines: lines = File.readlines("notes.txt", chomp: true)
Ruby opens, reads, and closes a file in a single File.read call — no unit number, no open/close pairing, and no read-loop terminated by an iostat code. each_line streams lines, and readlines returns them all as an array.