PONY λ M2 Modula-2

Pascal.CodeCompared.To/Ruby

An interactive executable cheatsheet comparing Pascal and Ruby

Free Pascal 3.2.2 Ruby 4.0
Hello World & Running
Hello, World
program HelloWorld; begin writeln('Hello, World!'); end.
puts "Hello, World!"
Ruby has no program header, no begin/end. wrapper, and no compilation step. Top-level code runs top to bottom the moment the file is loaded. puts writes its argument followed by a newline — the everyday equivalent of writeln.
Compiling vs running
{ Pascal is compiled ahead of time: fpc hello.pas && ./hello Optimised build: fpc -O2 hello.pas The result is a standalone native binary with no runtime dependency. }
# Ruby is interpreted — no build step, no binary: # ruby hello.rb # # Run a one-liner straight from the shell: # ruby -e 'puts "Hello, World!"' puts "Ruby runs the source directly"
Free Pascal compiles to a native executable you ship on its own. Ruby ships the source and interprets it at run time — there is no separate compile phase, and the script you edit is exactly the program that runs. The trade is portability and edit-run speed against the native performance and standalone binary you get from fpc.
Comments
program Comments; begin { This is a brace comment } (* This is an old-style comment *) // This is a line comment writeln('Comments shown above'); end.
# This is a line comment — the only everyday kind. =begin This is a block comment, rarely used. It must start in column one. =end puts "Comments shown above"
Ruby has a single line-comment character, #, used for everything. Its block-comment form (=begin/=end) exists but is rare — Rubyists simply prefix each line with #. There is no equivalent of Pascal's three interchangeable comment styles.
Formatted output
program PrintFormat; uses SysUtils; var name: string; age: integer; pi: double; begin name := 'Alice'; age := 30; pi := 3.14159; writeln(Format('Name: %s, Age: %d, Pi: %.2f', [name, age, pi])); writeln(name, ' is ', age, ' years old'); end.
name = "Alice" age = 30 pi = 3.14159 puts format("Name: %s, Age: %d, Pi: %.2f", name, age, pi) puts "#{name} is #{age} years old"
Ruby's format (also written "...".%(...)) uses the same %s/%d/%.2f specifiers as Pascal's Format. But the idiomatic way to build a string is interpolation: #{...} inside a double-quoted string evaluates any expression and inserts its value. There is no uses SysUtils to add — these are built in.
Variables & Types
Variables — no declarations
program Variables; var count: integer; message: string; ratio: double; flag: boolean; begin count := 42; message := 'hello'; ratio := 3.14; flag := true; writeln(count, ' ', message, ' ', ratio:0:2, ' ', flag); end.
count = 42 message = "hello" ratio = 3.14 flag = true puts "#{count} #{message} #{ratio} #{flag}"
Ruby has no var block and no type annotations. A variable springs into existence the first time you assign to it, and it can hold any object — an integer now, a string later. Assignment is plain =, not Pascal's :=; Ruby reserves = for assignment and == for comparison, so the two can never be confused.
Dynamic typing
program DynamicTyping; var value: integer; begin value := 42; { A Pascal variable's type is fixed at compile time. value := 'hello'; <- compile error: incompatible types } writeln(value); writeln('Type is fixed at compile time'); end.
value = 42 puts value.class # Integer value = "hello" # legal — the variable is just a name puts value.class # String value = [1, 2, 3] puts value.class # Array
In Pascal a variable is its type — the compiler binds the two and rejects any mismatch. In Ruby a variable is only a name that points at an object, and the same name may point at objects of different classes over its life. Every object carries its own class, which you can ask for at any time with .class.
Everything is an object
program Primitives; begin { In Pascal, an integer is a raw machine value. You cannot call a method on it. } writeln(Abs(-5)); { stand-alone function } writeln(Sqr(4)); { stand-alone function } writeln(42); end.
# In Ruby, 42 is a full object with methods: puts(-5.abs) # 5 puts 4 ** 2 # 16 puts 42.even? # true puts 42.class # Integer puts 42.is_a?(Numeric) # true
There are no primitives in Ruby. Integers, floats, booleans, and even nil are objects with classes and methods — (-5).abs, 42.even?, 3.times. Where Pascal offers stand-alone functions like Abs and Sqr that take a value, Ruby sends a message to the value itself. This single rule — everything is an object — shapes the entire language.
Constants
program Constants; const MAX_SIZE = 100; PI = 3.14159265; GREETING = 'Hello'; begin writeln('Max: ', MAX_SIZE); writeln('Pi: ', PI:0:8); writeln(GREETING); end.
MAX_SIZE = 100 PI = 3.14159265 GREETING = "Hello" puts "Max: #{MAX_SIZE}" puts "Pi: #{PI}" puts GREETING # Reassigning only warns — Ruby constants are a convention: # MAX_SIZE = 200 # warning: already initialized constant
Ruby marks a constant by capitalising its first letter — any identifier starting with an uppercase letter is a constant, and that includes every class and module name. Unlike Pascal's const, the value is not truly immutable: reassigning it merely prints a warning. The capital letter signals intent rather than enforcing it.
nil vs Pascal nil
program NilValue; type PInteger = ^integer; var pointer: PInteger; begin pointer := nil; { nil is only for pointers } if pointer = nil then writeln('Pointer is nil'); { Plain integers and strings can never be nil. } end.
value = nil # any variable can hold nil puts value.nil? # true puts value.class # NilClass — nil is an object too! puts value.inspect # nil # nil and false are the ONLY falsy values: puts "falsy" unless value puts "zero is truthy" if 0
Pascal's nil is strictly a null pointer; ordinary values can never be nil. Ruby's nil is a universal "no value" that any variable may hold, and it is itself an object (the sole instance of NilClass) with methods like .nil?. Crucially, only nil and false are falsy — 0 and the empty string are both truthy, unlike in many other languages.
Strings
String basics
program StringBasics; var greeting: string; name: string; full: string; begin greeting := 'Hello'; name := 'Alice'; full := greeting + ', ' + name + '!'; writeln(full); writeln(Length(full)); end.
greeting = "Hello" name = "Alice" full = "#{greeting}, #{name}!" # interpolation puts full puts full.length # method on the string itself
A Ruby string is a full object that knows its own length: full.length, not Length(full). Concatenation with + works as in Pascal, but interpolation ("#{greeting}, #{name}!") is the idiomatic way to assemble text. Single-quoted strings are literal; only double-quoted strings interpolate and honour escapes like \n.
String operations
program StringOps; uses SysUtils; var text: string; begin text := ' Hello, World! '; writeln(Trim(text)); writeln(UpperCase(text)); writeln(LowerCase(text)); writeln(Pos('World', text)); writeln(Copy(text, 3, 5)); end.
text = " Hello, World! " puts text.strip # Trim puts text.upcase # UpperCase puts text.downcase # LowerCase puts text.index("World") # zero-based position (or nil) puts text.strip[0, 5] # substring: start, length
Where Pascal calls SysUtils functions on a string (Trim(text), UpperCase(text)), Ruby calls methods on the string (text.strip, text.upcase). The method library is enormous and chainable: text.strip.upcase.reverse. Note index returns a zero-based offset or nil if not found, where Pascal's Pos returns a one-based position or 0.
Mutable strings & frozen literals
program StringMutation; var text: string; begin text := 'Hello'; text := text + ' World'; { builds a new string } text[1] := 'J'; { in-place: 1-based index } writeln(text); { Jello World } end.
# String literals are frozen in Ruby 4.0. # Use unary + (or .dup) for a mutable copy: text = +"Hello" text << " World" # << appends in place text[0] = "J" # zero-based index assignment puts text # Jello World
Ruby strings are mutable objects, and like Pascal you can assign to an individual character — but Ruby indexes from zero. As of Ruby 4.0 string literals are frozen by default, so mutating one raises FrozenError; prefix the literal with unary + (or call .dup) to get a fresh mutable copy. The << operator appends in place, avoiding the new-string allocation that Pascal's + performs.
Splitting & joining
program SplitJoin; uses Classes; var parts: TStringList; index: integer; begin parts := TStringList.Create; try parts.Delimiter := ','; parts.StrictDelimiter := True; parts.DelimitedText := '1,2,3,4'; for index := 0 to parts.Count - 1 do write(parts[index], ' '); writeln; parts.Delimiter := '-'; writeln(parts.DelimitedText); finally parts.Free; end; end.
parts = "1,2,3,4".split(",") puts parts.inspect # ["1", "2", "3", "4"] parts.each { |word| print word, " " } puts puts parts.join("-") # 1-2-3-4
Both languages split a string into pieces on a delimiter and join them back. In Ruby, split and join are methods on the string and array respectively, and the resulting array is a first-class object you can immediately iterate with a block. Free Pascal has no string-array literal type, so the conventional route is a TStringList whose DelimitedText property both parses and reassembles a delimited string.
Regular expressions
program RegexDemo; uses RegExpr; var matcher: TRegExpr; begin matcher := TRegExpr.Create('\d+'); try if matcher.Exec('abc123def456') then writeln('First match: ', matcher.Match[0]); finally matcher.Free; end; end.
text = "abc123def456" # Regex literals are built into the language: /.../ if text =~ /\d+/ puts "First match: #{$~[0]}" end puts text.scan(/\d+/).inspect # ["123", "456"]
Regular expressions are part of Ruby's syntax, written between slashes (/\d+/) with no library to import and no object to create and free. The =~ operator tests a match; scan returns every match as an array. Compare the RegExpr unit in Pascal, where you create a TRegExpr, call Exec, and must Free it in a finally block.
Numbers
Integers — one type, no overflow
program IntTypes; var small: shortint; { 8-bit } medium: integer; { 32-bit } large: int64; { 64-bit } begin small := 127; medium := 2000000; large := 9000000000; writeln(small, ' ', medium, ' ', large); { Exceeding int64 overflows silently. } end.
small = 127 medium = 2_000_000 large = 9_000_000_000 puts "#{small} #{medium} #{large}" # Integers grow without limit — no overflow: huge = 2 ** 200 puts huge puts huge.class # Integer
Ruby has a single Integer type with arbitrary precision: when a value exceeds machine-word size, Ruby transparently switches to a bignum representation, so 2 ** 200 is exact. There is no shortint/integer/int64 ladder to choose from and no silent overflow to guard against. Underscores may be used as digit separators, just as in Pascal.
Integer vs float division
program Division; var a, b: integer; begin a := 7; b := 2; writeln(a div b); { integer division: 3 } writeln(a mod b); { remainder: 1 } writeln(a / b:0:4); { real division: 3.5000 } end.
a = 7 b = 2 puts a / b # 3 — integer / integer is integer division puts a % b # 1 — remainder puts a.fdiv(b) # 3.5 — force float division puts a.to_f / b # 3.5 — or convert first
Ruby has no separate div operator: / performs integer division when both operands are integers (so 7 / 2 is 3) and float division when either is a float. This catches Pascal programmers who expect / to always yield a real. Use .fdiv or convert one operand with .to_f to force a floating-point result; % is the remainder, like mod.
Math functions
program MathFns; uses Math; begin writeln(Sqrt(2.0):0:6); writeln(Power(2.0, 10.0):0:0); writeln(Round(3.7)); writeln(Trunc(3.7)); writeln(Abs(-5)); end.
puts Math.sqrt(2) # 1.4142135623730951 puts 2 ** 10 # 1024 — exponent operator puts 3.7.round # 4 puts 3.7.to_i # 3 — truncate toward zero puts(-5.abs) # 5
Trigonometric and transcendental functions live in the Math module (Math.sqrt, Math.sin), parallel to Pascal's Math unit. But rounding and absolute value are methods on the number itself: 3.7.round, (-5).abs. Exponentiation has a dedicated operator, **, rather than a Power function.
Arrays
Array literals
program ArrayBasics; var numbers: array[0..4] of integer; index: integer; begin numbers[0] := 10; numbers[1] := 20; numbers[2] := 30; numbers[3] := 40; numbers[4] := 50; for index := 0 to High(numbers) do write(numbers[index], ' '); writeln; end.
numbers = [10, 20, 30, 40, 50] puts numbers[0] # 10 puts numbers[-1] # 50 — negative indexes count from the end puts numbers.length # 5 puts numbers.inspect # [10, 20, 30, 40, 50]
A Ruby array is written as a literal in square brackets and grows on demand — no fixed bounds, no SetLength. It can hold objects of mixed classes. Indexing is zero-based like a C-mode Pascal array, and negative indexes count back from the end (numbers[-1] is the last element), a convenience Pascal lacks.
Growing an array
program DynArray; var numbers: array of integer; index: integer; begin SetLength(numbers, 0); for index := 1 to 5 do begin SetLength(numbers, Length(numbers) + 1); numbers[High(numbers)] := index * 10; end; writeln('Length: ', Length(numbers)); end.
numbers = [] (1..5).each { |index| numbers << index * 10 } puts "Length: #{numbers.length}" puts numbers.inspect # [10, 20, 30, 40, 50] numbers.push(60, 70) # push multiple puts numbers.last # 70
Ruby arrays resize themselves: numbers << value (or push) appends in place, with no SetLength call and no manual High bookkeeping. Because the runtime manages the backing storage and a garbage collector reclaims it, the explicit grow-by-one pattern that Pascal forces simply disappears.
Transforming with map & select
program ArrayTransform; const numbers: array[1..5] of integer = (1, 2, 3, 4, 5); var doubled: array[1..5] of integer; index: integer; begin for index := 1 to 5 do doubled[index] := numbers[index] * 2; for index := 1 to 5 do write(doubled[index], ' '); writeln; end.
numbers = [1, 2, 3, 4, 5] doubled = numbers.map { |number| number * 2 } puts doubled.inspect # [2, 4, 6, 8, 10] evens = numbers.select { |number| number.even? } puts evens.inspect # [2, 4] total = numbers.reduce(0) { |sum, number| sum + number } puts total # 15
This is the single biggest shift for a Pascal programmer. Instead of an index loop that builds a result array element by element, Ruby gives every collection higher-order methods: map transforms each element, select keeps those matching a condition, and reduce folds the collection to one value. The { |number| ... } is a block — a chunk of behaviour passed to the method.
Slicing & combining
program ArraySlice; var numbers: array[0..5] of integer; index: integer; begin for index := 0 to 5 do numbers[index] := index; { No built-in slice — copy a range manually. } for index := 1 to 3 do write(numbers[index], ' '); writeln; end.
numbers = [0, 1, 2, 3, 4, 5] puts numbers[1..3].inspect # [1, 2, 3] — range slice puts numbers[1, 2].inspect # [1, 2] — start, length puts numbers.first(2).inspect # [0, 1] puts (numbers + [6, 7]).inspect # concatenate puts numbers.reverse.inspect # [5, 4, 3, 2, 1, 0]
Ruby slices an array with a range (numbers[1..3]) or a start/length pair (numbers[1, 2]), returning a new array. Arrays support + to concatenate, - to subtract elements, & for intersection, and dozens of query methods — operations that in Pascal you would write out by hand with index loops.
Hashes & Dictionaries
Hashes vs TDictionary
program HashBasics; uses Generics.Collections; var ages: specialize TDictionary<string, integer>; begin ages := specialize TDictionary<string, integer>.Create; try ages.Add('Alice', 30); ages.Add('Bob', 25); writeln('Alice is ', ages['Alice']); writeln('Count: ', ages.Count); finally ages.Free; end; end.
ages = { "Alice" => 30, "Bob" => 25 } puts "Alice is #{ages["Alice"]}" puts "Count: #{ages.size}" ages["Carol"] = 28 # add a pair puts ages.key?("Bob") # true puts ages.inspect
Ruby's Hash is the built-in key-value map, written as a literal with => between key and value. There is no generic type parameter to specialise, no Create, and no Free — the garbage collector owns it. Keys and values may be any objects, and a hash literal is as ordinary as an array literal, where Pascal needs the Generics.Collections unit and an explicit TDictionary.
Symbol keys
program HashSymbols; uses Generics.Collections; var config: specialize TDictionary<string, string>; begin config := specialize TDictionary<string, string>.Create; try config.Add('host', 'localhost'); config.Add('port', '8080'); writeln(config['host'], ':', config['port']); finally config.Free; end; end.
# Symbols (:name) are the idiomatic hash key: config = { host: "localhost", port: "8080" } puts "#{config[:host]}:#{config[:port]}" # :host is a Symbol — an immutable, interned identifier: puts :host.class # Symbol puts config.keys.inspect # [:host, :port]
Ruby has a type with no Pascal analogue: the symbol, written :name. A symbol is an immutable, interned identifier — the same :host is one shared object everywhere it appears, which makes symbols ideal, memory-cheap hash keys. The { host: "..." } shorthand is sugar for { :host => "..." }. Think of a symbol as a name used as a value.
Iterating a hash
program HashIterate; uses Generics.Collections; var ages: specialize TDictionary<string, integer>; pair: specialize TPair<string, integer>; begin ages := specialize TDictionary<string, integer>.Create; try ages.Add('Alice', 30); ages.Add('Bob', 25); for pair in ages do writeln(pair.Key, ' => ', pair.Value); finally ages.Free; end; end.
ages = { "Alice" => 30, "Bob" => 25 } ages.each do |name, age| puts "#{name} => #{age}" end # Hashes preserve insertion order in Ruby: puts ages.map { |name, age| "#{name}:#{age}" }.join(", ")
Iterating a hash yields each key and value straight into the block's two parameters — |name, age| — with no TPair wrapper to unpack. Ruby hashes also guarantee insertion order when iterated, which Pascal's TDictionary does not promise. And the same map/select/reduce family that works on arrays works on hashes too.
Default values
program HashDefault; uses Generics.Collections; var counts: specialize TDictionary<string, integer>; existing: integer; begin counts := specialize TDictionary<string, integer>.Create; try if not counts.TryGetValue('apple', existing) then existing := 0; counts.AddOrSetValue('apple', existing + 1); writeln(counts['apple']); finally counts.Free; end; end.
# A Hash can carry a default for missing keys: counts = Hash.new(0) "apple banana apple cherry apple".split.each do |fruit| counts[fruit] += 1 end puts counts.inspect # {"apple"=>3, "banana"=>1, "cherry"=>1}
Passing a default to Hash.new(0) makes every missing key read back as 0 instead of nil, so the classic count-the-occurrences loop becomes a one-liner counts[fruit] += 1. In Pascal you must TryGetValue and supply the fallback yourself on every access — Ruby folds that into the hash itself.
Sets & Ranges
Sets — built in vs require
program SetType; type TColor = (Red, Green, Blue, Yellow); TColors = set of TColor; var chosen: TColors; begin chosen := [Red, Blue]; if Red in chosen then writeln('Red is chosen'); Include(chosen, Green); chosen := chosen - [Blue]; writeln(Green in chosen, ' ', Blue in chosen); end.
require "set" chosen = Set[:red, :blue] puts chosen.include?(:red) # true chosen << :green # add chosen.delete(:blue) # remove puts chosen.include?(:green) # true puts chosen.include?(:blue) # false puts (Set[1, 2, 3] & Set[2, 3, 4]).inspect # intersection
Here Pascal is the more elegant language: set of is a first-class type with in, +, -, and * operators built straight into the syntax. Ruby keeps Set in the standard library, so you require "set" first; thereafter it offers include?, & (intersection), | (union), and - (difference). Pascal's sets are bit-packed and limited to small ordinal types; Ruby's hold any objects.
Ranges
program RangeType; type TMonth = 1..12; var month: TMonth; index: integer; begin { A subrange is a TYPE constraint, checked with {$R+}. } for index := 1 to 12 do write(index, ' '); writeln; month := 6; writeln('Month: ', month); end.
months = 1..12 # a Range object puts months.include?(6) # true puts months.to_a.inspect # [1, 2, ... 12] puts months.sum # 78 # Ranges work on more than integers: puts ("a".."e").to_a.inspect # ["a", "b", "c", "d", "e"] (1...5).each { |n| print n, " " } # ... excludes the end puts
A Pascal subrange like 1..12 is a compile-time type that constrains a variable. A Ruby Range like 1..12 is a runtime object you can iterate, test with include?, convert to an array, or sum. Two dots include the endpoint; three dots (1...5) exclude it. Ranges also span characters and other comparable objects, not just integers.
Control Flow
if / elsif / else
program IfElse; var score: integer; begin score := 75; if score >= 90 then writeln('A') else if score >= 80 then writeln('B') else if score >= 70 then writeln('C') else writeln('F'); end.
score = 75 if score >= 90 puts "A" elsif score >= 80 # note the spelling: elsif puts "B" elsif score >= 70 puts "C" else puts "F" end
Ruby's conditional needs no then and no parentheses around the condition, and it closes with a single end rather than relying on statement nesting. The chained keyword is elsif — one word, no second e — which trips up newcomers. Any block (if, while, def, class) terminates with end, Ruby's universal equivalent of Pascal's begin/end pairs.
Statement modifiers
program ModifierIf; var age: integer; begin age := 20; if age >= 18 then writeln('Adult'); { Pascal always needs the full if/then statement. } end.
age = 20 puts "Adult" if age >= 18 # trailing if puts "Minor" unless age >= 18 # unless = if not # A whole loop can trail too: print "go " while (age -= 5) > 0 puts
Ruby lets you append if, unless, or while to the end of a statement — puts "Adult" if age >= 18 reads almost like English and is the preferred form for a one-line guard. unless is simply if not, with no Pascal counterpart. There is no equivalent in Pascal, where every conditional must be a full if...then statement.
case / when
program CaseOf; var day: integer; begin day := 3; case day of 1: writeln('Monday'); 2: writeln('Tuesday'); 3: writeln('Wednesday'); 6, 7: writeln('Weekend'); else writeln('Other'); end; end.
day = 3 result = case day when 1 then "Monday" when 2 then "Tuesday" when 3 then "Wednesday" when 6, 7 then "Weekend" else "Other" end puts result
Ruby's case/when mirrors Pascal's case/of — multiple values per branch (when 6, 7), an else fallback, and no fall-through. Two differences matter: each branch compares with the === operator (so a when can match a range, class, or regex, not just a constant), and the whole case is an expression that returns a value you can assign directly.
case matching on class & range
program CaseClass; var value: integer; begin value := 42; { Pascal case only matches ordinal constants; you cannot switch on a type or a range expression the way Ruby can. } case value of 0..9: writeln('single digit'); 10..99: writeln('two digits'); else writeln('big'); end; end.
value = 42 description = case value when 0..9 then "single digit" when 10..99 then "two digits" when Integer then "big integer" when String then "some text" else "unknown" end puts description
Because when uses ===, a Ruby case can match a range (0..9), a class (Integer tests whether the value is an integer), or a regular expression — all in the same statement. Pascal's case supports range labels too, but it can only ever branch on ordinal constants; it has no way to ask "is this value of this type?" at runtime.
Loops & Iteration
Counting loops
program ForLoop; var index: integer; begin for index := 1 to 5 do write(index, ' '); writeln; for index := 5 downto 1 do write(index, ' '); writeln; end.
(1..5).each { |index| print index, " " } puts 5.downto(1) { |index| print index, " " } puts 3.times { |index| print "tick " } # 0, 1, 2 puts
Ruby has a for keyword, but Rubyists almost never use it. The idiomatic counting loop is a method on a range or integer: (1..5).each, 5.downto(1), 3.times. The loop variable arrives as a block parameter and is scoped to the block. This reflects the deeper pattern — iteration in Ruby is a method call that yields to a block, not a built-in control structure.
Iterating a collection
program ForEach; var fruits: array[0..2] of string; fruit: string; begin fruits[0] := 'apple'; fruits[1] := 'banana'; fruits[2] := 'cherry'; for fruit in fruits do writeln(fruit); end.
fruits = ["apple", "banana", "cherry"] fruits.each { |fruit| puts fruit } # each_with_index gives the position too: fruits.each_with_index do |fruit, index| puts "#{index}: #{fruit}" end
Modern Pascal's for...in walks a collection, and Ruby's each is its direct counterpart — except each is a method that hands each element to a block. When you also need the index, each_with_index yields both, sparing you the manual counter. The block form ({ |fruit| ... } or do |fruit| ... end) is the heartbeat of Ruby iteration.
while & until
program WhileLoop; var count: integer; begin count := 1; while count <= 5 do begin write(count, ' '); Inc(count); end; writeln; count := 1; repeat write(count, ' '); Inc(count); until count > 5; writeln; end.
count = 1 while count <= 5 print count, " " count += 1 end puts count = 1 until count > 5 # until = while not print count, " " count += 1 end puts
Ruby's while matches Pascal's, closing with end instead of begin/end. In place of Pascal's repeat...until, Ruby offers until, which loops while a condition is false — the negation of while. Ruby has no Inc; use the compound assignment count += 1.
break, next & loop
program LoopControl; var index: integer; begin for index := 1 to 10 do begin if index = 8 then Break; if index mod 2 = 0 then Continue; write(index, ' '); end; writeln; end.
(1..10).each do |index| break if index == 8 # leave the loop next if index.even? # skip to the next iteration print index, " " end puts # 1 3 5 7 # loop runs forever until break: count = 0 loop do count += 1 break if count > 3 end puts count # 4
Ruby's break matches Pascal's Break, and next plays the role of Pascal's Continue — skipping to the next iteration. Ruby adds loop do ... end, an unconditional infinite loop you exit with break, handy when the exit test is most natural in the middle of the body.
Procedures & Functions
Procedures & functions become methods
program Methods; procedure Greet(name: string); begin writeln('Hello, ', name, '!'); end; function Square(n: integer): integer; begin Result := n * n; end; begin Greet('Alice'); writeln(Square(5)); end.
def greet(name) puts "Hello, #{name}!" end def square(n) n * n # last expression is the return value end greet("Alice") puts square(5)
Ruby has one keyword, def, for what Pascal splits into procedure and function. There is no return-type declaration and no parameter types. Most strikingly, there is no Result variable and usually no return: a method implicitly returns the value of its last expression, so square just ends with n * n.
Default & keyword arguments
program DefaultArgs; function Greeting(name: string; salutation: string = 'Hello'): string; begin Result := salutation + ', ' + name + '!'; end; begin writeln(Greeting('Alice')); writeln(Greeting('Bob', 'Hi')); end.
def greeting(name, salutation: "Hello") "#{salutation}, #{name}!" end puts greeting("Alice") # Hello, Alice! puts greeting("Bob", salutation: "Hi") # Hi, Bob! # Default that is just positional: def power(base, exponent = 2) = base ** exponent puts power(5) # 25
Both languages support default parameter values. Ruby adds keyword argumentssalutation: names the parameter at the call site, so argument order stops mattering and the call documents itself. The def power(base, exponent = 2) = ... form is Ruby's "endless method", a one-line definition mirroring Pascal's compact functions.
Returning multiple values
program MultiReturn; procedure DivMod(a, b: integer; var quotient, remainder: integer); begin quotient := a div b; remainder := a mod b; end; var q, r: integer; begin DivMod(17, 5, q, r); writeln(q, ' remainder ', r); end.
def div_mod(a, b) [a / b, a % b] # return an array end quotient, remainder = div_mod(17, 5) # destructure it puts "#{quotient} remainder #{remainder}"
Pascal returns extra values through var parameters that the caller must declare in advance. Ruby returns an array and lets the caller destructure it in one assignment: quotient, remainder = div_mod(17, 5). Parallel assignment like this works anywhere, and it removes the need for pass-by-reference output parameters entirely.
Variable arguments
program VarArgs; function SumAll(const values: array of integer): integer; var value: integer; begin Result := 0; for value in values do Result := Result + value; end; begin writeln(SumAll([1, 2, 3, 4, 5])); end.
def sum_all(*values) # * gathers extra args into an array values.reduce(0) { |total, value| total + value } end puts sum_all(1, 2, 3, 4, 5) # 15 puts sum_all(10, 20) # 30 numbers = [1, 2, 3] puts sum_all(*numbers) # * also splats an array into args
Ruby's splat operator, *, gathers any number of trailing arguments into an array inside the method (def sum_all(*values)) and, at a call site, expands an array back into separate arguments (sum_all(*numbers)). Pascal's array of open-array parameter is the closest analogue, but it requires the caller to wrap the values in an array literal.
Blocks & Closures
Blocks — Ruby's defining feature
program Blocks; type TIntProc = procedure(n: integer); procedure Repeat3(action: TIntProc); var index: integer; begin for index := 1 to 3 do action(index); end; procedure ShowSquare(n: integer); begin writeln(n, ' squared is ', n * n); end; begin Repeat3(@ShowSquare); end.
# A block is an anonymous chunk of code passed to a method. # 'yield' runs whatever block the caller supplied: def repeat3 (1..3).each { |index| yield index } end repeat3 { |n| puts "#{n} squared is #{n * n}" }
The block is the feature with no real Pascal equivalent. In Pascal you declare a procedure type, define a named procedure, and pass @ShowSquare as a function pointer. In Ruby you pass an inline, anonymous block right at the call, and the method runs it with yield. Almost every iteration method you have already seen — each, map, times — is built on this one mechanism.
Brace vs do/end blocks
program BlockStyles; const numbers: array[1..3] of integer = (1, 2, 3); var index: integer; begin { Pascal has no block syntax; this is a plain loop. } for index := 1 to 3 do begin writeln(numbers[index] * 10); end; end.
numbers = [1, 2, 3] # Braces for a one-liner: numbers.each { |number| puts number * 10 } # do/end for multi-line bodies: numbers.each do |number| squared = number * number puts "#{number} -> #{squared}" end
A block has two interchangeable forms: curly braces { |x| ... } for a single line, and do |x| ... end for multi-line bodies. The pipes around the parameters (|number|) are how a block declares the arguments it receives. By convention braces are for short, value-returning blocks and do/end for blocks run for their side effects — a stylistic distinction Pascal's begin/end never needs to make.
Closures capture their scope
program Closures; { Free Pascal supports nested closures via "nested" functions or anonymous methods, but they are an advanced feature and do not capture variables as freely as Ruby blocks. } var base: integer; function AddBase(n: integer): integer; begin Result := n + base; { captures the outer 'base' } end; begin base := 100; writeln(AddBase(5)); { 105 } end.
def make_adder(base) ->(n) { n + base } # a lambda that captures 'base' end add_ten = make_adder(10) add_hundred = make_adder(100) puts add_ten.call(5) # 15 puts add_hundred.call(5) # 105
A Ruby lambda (->(n) { ... }) or proc is a block you store in a variable and call later, and it closes over the variables in scope when it was created — each make_adder call captures its own base. Call it with .call (or .()). Free Pascal's nested functions and anonymous methods can capture variables too, but the lightweight, store-and-pass-anywhere closure is far more central to everyday Ruby.
Symbol-to-proc shorthand
program MapMethod; uses SysUtils; var words: array[0..2] of string; index: integer; begin words[0] := 'hello'; words[1] := 'world'; words[2] := 'ruby'; { Apply a function to each element by hand. } for index := 0 to 2 do writeln(UpperCase(words[index])); end.
words = ["hello", "world", "ruby"] # These two lines are equivalent: puts words.map { |word| word.upcase }.inspect puts words.map(&:upcase).inspect # &:upcase turns the symbol :upcase into a block # that calls .upcase on each element.
When a block does nothing but call one method on each element, Ruby offers a terse shorthand: &:upcase is equivalent to { |word| word.upcase }. The & converts the symbol :upcase into a block. You will see map(&:to_i), select(&:even?), and the like constantly — there is simply no Pascal equivalent, because Pascal has neither blocks nor symbols.
Records & Classes
Records & classes
program ClassBasics; type TPerson = class private FName: string; FAge: integer; public constructor Create(AName: string; AAge: integer); procedure Greet; end; constructor TPerson.Create(AName: string; AAge: integer); begin FName := AName; FAge := AAge; end; procedure TPerson.Greet; begin writeln('Hi, I am ', FName); end; var person: TPerson; begin person := TPerson.Create('Alice', 30); try person.Greet; finally person.Free; end; end.
class Person def initialize(name, age) # the constructor @name = name # @ marks an instance variable @age = age end def greet puts "Hi, I am #{@name}" end end person = Person.new("Alice", 30) person.greet # No Free — the garbage collector reclaims it
Ruby's class declaration puts the method bodies inside the class — there is no split between interface and implementation as in a Pascal unit. The constructor is always named initialize and is invoked by Person.new. Instance variables are prefixed with @ and need no declaration; they appear when first assigned. And because Ruby is garbage-collected, there is no Free and no try...finally to balance it.
Properties & accessors
program Properties; type TCounter = class private FValue: integer; public property Value: integer read FValue write FValue; end; var counter: TCounter; begin counter := TCounter.Create; try counter.Value := 10; writeln(counter.Value); finally counter.Free; end; end.
class Counter attr_accessor :value # generates reader AND writer def initialize @value = 0 end end counter = Counter.new counter.value = 10 # calls the writer method puts counter.value # calls the reader method
Pascal's property Value read FValue write FValue generates getter/setter access to a field. Ruby's attr_accessor :value does the same in one line, creating both a value reader and a value= writer. Use attr_reader for read-only or attr_writer for write-only. Under the hood counter.value = 10 is a real method call to value= — assignment syntax that dispatches to a method.
Lightweight value objects
program ValueRecord; type TPoint = record x, y: double; end; var origin: TPoint; begin origin.x := 3.0; origin.y := 4.0; writeln('(', origin.x:0:1, ', ', origin.y:0:1, ')'); end.
# Struct builds a small class with accessors in one line: Point = Struct.new(:x, :y) do def distance_from_origin Math.sqrt(x ** 2 + y ** 2) end end origin = Point.new(3.0, 4.0) puts "(#{origin.x}, #{origin.y})" puts origin.distance_from_origin # 5.0
For a simple bundle of named fields — Pascal's record — Ruby offers Struct.new(:x, :y), which manufactures a class complete with a constructor and accessors. You can attach methods to it in the block, blurring the line Pascal draws between a passive record and an active class. (Ruby 3.2+ also has Data.define for immutable value objects.)
self vs Self
program SelfRef; type TAccount = class private FBalance: integer; public function Deposit(amount: integer): TAccount; end; function TAccount.Deposit(amount: integer): TAccount; begin FBalance := FBalance + amount; Result := Self; { return the instance for chaining } end; var account: TAccount; begin account := TAccount.Create; try account.Deposit(100).Deposit(50); writeln('Balance updated'); finally account.Free; end; end.
class Account def initialize @balance = 0 end def deposit(amount) @balance += amount self # return the instance for chaining end def balance = @balance end account = Account.new account.deposit(100).deposit(50) puts account.balance # 150
Both languages use a self-reference for the current instance: Pascal's Self and Ruby's lowercase self. Returning self from a method enables fluent chaining (account.deposit(100).deposit(50)). Inside a Ruby method you rarely write self to read an instance variable — @balance is already in scope — but you do return it explicitly when you want the method to be chainable.
Inheritance & Modules
Inheritance & virtual methods
program Inheritance; type TAnimal = class public function Speak: string; virtual; end; TDog = class(TAnimal) public function Speak: string; override; end; function TAnimal.Speak: string; begin Result := '...'; end; function TDog.Speak: string; begin Result := 'Woof!'; end; var animal: TAnimal; begin animal := TDog.Create; try writeln(animal.Speak); { Woof! } finally animal.Free; end; end.
class Animal def speak "..." end end class Dog < Animal # < means "inherits from" def speak # overrides — no keyword needed "Woof!" end end animal = Dog.new puts animal.speak # Woof!
Ruby denotes inheritance with < (class Dog < Animal) and supports only single inheritance, like Pascal. The big simplification: every method is "virtual" automatically. There is no virtual/override bookkeeping — defining a method with the same name in a subclass simply overrides it, and method lookup is always dynamic. Call the parent's version with the keyword super.
Calling the parent — super
program SuperCall; type TBase = class public constructor Create; virtual; end; TDerived = class(TBase) public constructor Create; override; end; constructor TBase.Create; begin writeln('Base init'); end; constructor TDerived.Create; begin inherited Create; { call the parent constructor } writeln('Derived init'); end; var obj: TDerived; begin obj := TDerived.Create; obj.Free; end.
class Base def initialize puts "Base init" end end class Derived < Base def initialize super # call Base#initialize puts "Derived init" end end Derived.new
Where Pascal writes inherited Create to invoke the parent method, Ruby writes super. A bare super forwards the current method's arguments to the parent automatically; super(x, y) passes explicit ones, and super() passes none. It works in any method, not just the constructor.
Modules & mixins vs interfaces
program Interfaces; type IGreetable = interface function Greet: string; end; TRobot = class(TInterfacedObject, IGreetable) public function Greet: string; end; function TRobot.Greet: string; begin Result := 'BEEP BOOP'; end; var greetable: IGreetable; begin greetable := TRobot.Create; writeln(greetable.Greet); end.
module Greetable # a mixin, not an interface def greet "Hi, I am #{name}" # uses a method the includer provides end end class Robot include Greetable # mix the module's methods in def name = "R2D2" end puts Robot.new.greet # Hi, I am R2D2
A Pascal interface declares method signatures a class must implement. A Ruby module goes further: it supplies actual method bodies that you mix into a class with include. This is composition by mixin, and it sidesteps single inheritance — a class can include any number of modules. Standard mixins like Comparable and Enumerable hand you dozens of methods in exchange for implementing just one or two.
Comparable mixin
program Comparing; type TVersion = class public Major: integer; function CompareTo(other: TVersion): integer; end; function TVersion.CompareTo(other: TVersion): integer; begin Result := Major - other.Major; end; var a, b: TVersion; begin a := TVersion.Create; a.Major := 2; b := TVersion.Create; b.Major := 5; if a.CompareTo(b) < 0 then writeln('a is older'); a.Free; b.Free; end.
class Version include Comparable # gain <, >, ==, between?, clamp... attr_reader :major def initialize(major) = @major = major def <=>(other) # define ONE method, the spaceship major <=> other.major end end puts Version.new(2) < Version.new(5) # true puts [Version.new(5), Version.new(2)].min.major # 2
Implement the single <=> ("spaceship") method — which returns -1, 0, or 1 like Pascal's CompareTo — then include Comparable, and Ruby gives your class <, <=, ==, >, >=, between?, clamp, and array min/max/sort for free. This is the payoff of mixins: a little protocol unlocks a large, consistent interface.
Enumerations
Enumerated types
program Enums; type TColor = (Red, Green, Blue); var color: TColor; begin color := Green; case color of Red: writeln('Red'); Green: writeln('Green'); Blue: writeln('Blue'); end; writeln(Ord(color)); { 1 } end.
# Ruby has no enum type; symbols are the idiom: COLORS = [:red, :green, :blue] color = :green case color when :red then puts "Red" when :green then puts "Green" when :blue then puts "Blue" end puts COLORS.index(color) # 1 — position stands in for Ord
Ruby has no enumerated type. The idiomatic substitute is a set of symbols:red, :green, :blue — which are self-documenting, comparable, and usable as case labels. You lose the compiler's guarantee that a variable holds only a declared value, and there is no built-in Ord; if you need ordinal positions, store the symbols in an array and use index.
Named integer constants
program EnumValues; type TPriority = (Low = 1, Medium = 5, High = 10); var priority: TPriority; begin priority := High; writeln('Priority value: ', Ord(priority)); { 10 } end.
# Constants inside a module act as a namespaced enum: module Priority LOW = 1 MEDIUM = 5 HIGH = 10 end priority = Priority::HIGH puts "Priority value: #{priority}" # 10
When you need enum members with explicit integer values — Pascal's (Low = 1, ...) — group plain constants inside a module, which provides a namespace. You reach them with :: (Priority::HIGH), the scope-resolution operator. This is just integers with names, so there is no type safety, but it reads clearly and keeps the names from polluting the global scope.
Error Handling
Raising & rescuing
program Exceptions; uses SysUtils; begin try raise Exception.Create('something broke'); except on E: Exception do writeln('Caught: ', E.Message); end; end.
begin raise "something broke" # raises a RuntimeError rescue => error puts "Caught: #{error.message}" end
Ruby's begin/rescue/end is the counterpart to Pascal's try/except/end. raise matches Pascal's raise; given a bare string it creates a RuntimeError with that message. The rescue => error clause binds the exception object, whose .message holds the text — directly analogous to on E: Exception do and E.Message.
Rescuing specific types
program SpecificExceptions; uses SysUtils; var value: integer; begin try value := StrToInt('not a number'); writeln(value); except on E: EConvertError do writeln('Bad number: ', E.Message); on E: Exception do writeln('Other error: ', E.Message); end; end.
begin value = Integer("not a number") # raises ArgumentError puts value rescue ArgumentError => error puts "Bad number: #{error.message}" rescue StandardError => error puts "Other error: #{error.message}" end
Multiple rescue clauses match by exception class, most specific first, exactly like Pascal's stacked on E: SomeType do handlers. A bare rescue with no class catches StandardError (the everyday base class) — not every possible error, which protects you from accidentally swallowing signals and exits. Ruby's exception hierarchy parallels Pascal's, with StandardError playing the role of Exception.
ensure vs finally
program FinallyBlock; uses Classes; var resource: TStringList; begin resource := TStringList.Create; try resource.Add('working'); writeln(resource[0]); finally resource.Free; { always runs } writeln('Cleaned up'); end; end.
def process puts "working" raise "oops" if false ensure puts "Cleaned up" # always runs, error or not end begin process rescue => error puts "Caught: #{error.message}" end
Ruby's ensure is Pascal's finally: its body runs whether the protected code succeeds or raises. Because Ruby manages memory, ensure is needed less often than Pascal's finally — there is no Free to guarantee — but it remains essential for closing files, releasing locks, and other non-memory cleanup. Note ensure can attach directly to a method body, without an explicit begin.
Custom exception classes
program CustomException; uses SysUtils; type EValidationError = class(Exception); begin try raise EValidationError.Create('age must be positive'); except on E: EValidationError do writeln('Validation failed: ', E.Message); end; end.
class ValidationError < StandardError end begin raise ValidationError, "age must be positive" rescue ValidationError => error puts "Validation failed: #{error.message}" end
A custom exception is just a class that inherits from StandardError (Pascal subclasses Exception). The raise ValidationError, "message" form constructs the exception and sets its message in one step. Defining domain-specific exception classes — and rescuing them precisely — works the same in both languages; only the base class name differs.
Metaprogramming & Duck Typing
Duck typing
program DuckTyping; { Pascal cannot do this: a parameter's type is fixed, and only values of that type (or descendants) are accepted. You would need a common base class or interface. } type TSpeaker = class function Speak: string; virtual; abstract; end; begin writeln('Pascal requires a shared type'); end.
# No shared type needed — only that the object responds to .speak: class Dog; def speak = "Woof"; end class Cat; def speak = "Meow"; end class Robot; def speak = "BEEP"; end def announce(thing) puts thing.speak # works for ANY object with #speak end [Dog.new, Cat.new, Robot.new].each { |thing| announce(thing) }
This is the philosophical heart of the difference. Pascal binds a parameter to a type and accepts only that type or its descendants; sharing behaviour across unrelated classes requires a common base class or interface. Ruby asks no such question — announce calls thing.speak and works for any object that responds to speak, related or not. "If it walks like a duck and quacks like a duck, it is a duck."
Introspection
program Introspection; { Pascal's RTTI exists but is limited and verbose. There is no easy runtime question like "does this respond to X?". } type TWidget = class procedure Draw; end; procedure TWidget.Draw; begin writeln('drawing'); end; begin writeln('Pascal RTTI is limited'); end.
value = "hello" puts value.class # String puts value.respond_to?(:upcase) # true puts value.respond_to?(:push) # false puts value.methods.size > 100 # true — strings have many methods puts 42.is_a?(Numeric) # true puts value.frozen? # true (literals are frozen)
Ruby objects are fully introspectable at run time. You can ask any object for its .class, whether it .respond_to? a method, whether it .is_a? some class, or for the entire list of .methods it understands. Pascal has RTTI, but it is comparatively limited and verbose; the casual, everywhere-available reflection Ruby offers is a different category of capability, and it is what makes duck typing safe to rely on.
Open classes
program OpenClasses; { Pascal classes are closed: once compiled, you cannot add a method to an existing class such as the string type. A class helper can extend a type, but only within its own scope and with restrictions. } begin writeln('Pascal classes are closed at compile time'); end.
# Reopen the built-in Integer class and add a method: class Integer def factorial (1..self).reduce(1, :*) end end puts 5.factorial # 120 puts 0.factorial # 1
Ruby classes are open: you can reopen any class — even a built-in like Integer or String — and add or replace methods at run time. Suddenly every integer in the program has a factorial method. Pascal classes are sealed once compiled (class helpers offer a constrained, scoped imitation). This power demands restraint — carelessly patching core classes is the much-warned-about "monkey patching" — but it underlies much of Ruby's expressiveness, Rails included.
Dynamic methods
program DynamicMethods; { Pascal resolves every method call at compile time. There is no hook for "a method that does not exist was called", and no way to synthesise methods at run time. } begin writeln('Pascal has no method_missing equivalent'); end.
class FlexibleConfig def initialize @settings = {} end def method_missing(name, *args) text = name.to_s if text.end_with?("=") @settings[text.chomp("=").to_sym] = args.first else @settings[name] end end end config = FlexibleConfig.new config.host = "localhost" # no 'host=' method defined! puts config.host # localhost
When you call a method an object does not define, Ruby invokes method_missing with the method name and arguments — a hook that lets an object respond to messages it was never explicitly given, here turning arbitrary config.anything = x into hash storage. Pascal resolves every call at compile time and has nothing comparable. This run-time method dispatch is how Rails conjures methods like find_by_email that no one ever wrote.
I/O & Files
Reading input
program ReadInput; var name: string; begin Write('Enter your name: '); ReadLn(name); writeln('Hello, ', name, '!'); end.
print "Enter your name: " name = gets.chomp # gets reads a line incl. newline puts "Hello, #{name}!" # chomp strips the trailing newline
Ruby's gets reads one line from standard input — but unlike Pascal's ReadLn it keeps the trailing newline, so the idiom is gets.chomp to strip it. print writes without a newline (use it for prompts); puts adds one. This example cannot run in the browser, since there is no console to type into.
Writing a file
program WriteFile; var output: TextFile; begin AssignFile(output, 'greeting.txt'); Rewrite(output); try writeln(output, 'Hello from Pascal'); writeln(output, 'Second line'); finally CloseFile(output); end; end.
# The block form opens and auto-closes the file: File.open("greeting.txt", "w") do |file| file.puts "Hello from Ruby" file.puts "Second line" end # File is closed automatically when the block ends. puts "File written"
Pascal's file workflow is a four-step ritual: AssignFile, Rewrite, write, CloseFile — with a finally to guarantee the close. Ruby collapses this into File.open(name, "w") do |file| ... end: the block receives the open file and Ruby closes it automatically when the block exits, even on an exception. This block-with-cleanup pattern is everywhere in Ruby. (File I/O is unavailable in the browser sandbox.)
Reading a file
program ReadFile; var input: TextFile; line: string; begin AssignFile(input, 'data.txt'); Reset(input); try while not Eof(input) do begin ReadLn(input, line); writeln(line); end; finally CloseFile(input); end; end.
# Slurp the whole file: contents = File.read("data.txt") puts contents # Or iterate lines without loading it all at once: File.foreach("data.txt") do |line| puts line.chomp end # Read into an array of lines: lines = File.readlines("data.txt") puts lines.length
Reading collapses just as dramatically. File.read returns the entire file as one string; File.foreach streams it line by line for large files; File.readlines returns an array of lines. None require the explicit Reset/Eof/ReadLn/CloseFile loop Pascal needs — and each closes the file for you. (This example is marked non-runnable because the browser has no filesystem.)