PONY λ M2 Modula-2

Ruby.CodeCompared.To/Scheme

An interactive executable cheatsheet for Rubyists learning Scheme

Ruby 4.0 BiwaScheme 0.8.3 (R7RS-ish)
Output
Hello World
puts "Hello, World!"
(display "Hello, World!") (newline)
Scheme separates output and newline by design: display writes a value without surrounding quotes, and newline writes a single line break. There is no puts that does both in one step. The outer parentheses are a function call — display is the function, the string is the argument. Every call in Scheme uses this prefix form.
display vs write
puts "hello" # hello p "hello" # "hello" puts [1, 2, 3] # 1 # 2 # 3
(display "hello") (newline) ; hello (write "hello") (newline) ; "hello" (display '(1 2 3)) (newline) ; (1 2 3)
display is for human-readable output: strings appear without quotes, characters without the #\ prefix. write is for machine-readable output: strings appear quoted, characters as #\a, so the result is something the reader can parse back. The pair maps loosely to Ruby's puts vs p.
Comments
# Single-line comment total = 42 # inline puts total =begin Block comment =end
; Single-line comment ;; Top-level comment (conventional double semicolon) (define total 42) ; inline (display total) (newline) #| Block comment spanning lines |#
Scheme uses ; for line comments, with the convention that ;; opens a comment on its own line and ; follows code. Block comments use #|...|#. There is also #;, a reader-level "datum comment" that comments out the next entire form — #;(display 42) removes the call without touching characters around it.
Variables and Bindings
Top-level define
GREETING = "Hello" person = "World" puts "#{GREETING}, #{person}!"
(define greeting "Hello") (define person "World") (display greeting) (display ", ") (display person) (display "!") (newline)
define binds a name in the current scope — at the top level it creates a global, inside a body it creates a local. Scheme has no SCREAMING_SNAKE_CASE for constants: convention is kebab-case for ordinary identifiers. There is also no string interpolation in standard Scheme; concatenation happens by calling display repeatedly or with string-append.
let — parallel local bindings
radius = 5 area = Math::PI * radius ** 2 puts "Area: #{area.round(2)}"
(let ((radius 5) (pi 3.141592653589793)) (let ((area (* pi radius radius))) (display "Area: ") (display area) (newline)))
let creates a fresh scope and binds names in parallel — each binding sees only what was visible before the let, not the other bindings being introduced. That is why this example needs a nested let to compute area from radius. For sequential bindings, use let*.
let* — sequential bindings
x = 3 y = x * 2 z = x + y puts z
(let* ((x 3) (y (* x 2)) (z (+ x y))) (display z) (newline))
let* binds names one at a time, left to right — each binding can refer to the ones above it. This is the form to reach for when later values depend on earlier ones. Think of it as syntactic sugar for nested let expressions.
letrec — mutually recursive bindings
odd = nil # declare first so the lambda below can capture it even = ->(number) { number.zero? ? true : odd.call(number - 1) } odd = ->(number) { number.zero? ? false : even.call(number - 1) } puts even.call(10) # true
(letrec ((is-even? (lambda (n) (if (= n 0) #t (is-odd? (- n 1))))) (is-odd? (lambda (n) (if (= n 0) #f (is-even? (- n 1)))))) (display (is-even? 10)) (newline))
letrec makes every binding visible to every other binding in the same group — necessary when two functions need to call each other. Ruby has no direct equivalent: forward references between local lambdas require the workaround of declaring one variable first and assigning it afterwards. In Scheme the language ships the right tool.
set! — mutation
count = 0 3.times { count += 1 } puts count # 3
(define count 0) (define (increment!) (set! count (+ count 1))) (increment!) (increment!) (increment!) (display count) (newline)
set! mutates an existing binding — the trailing exclamation point is the universal Scheme convention for "this procedure mutates state". Idiomatic Scheme avoids mutation when it can: most functions return new values rather than modifying their inputs, and let/letrec bindings are immutable in spirit even though set! can change them.
Numbers and Arithmetic
Basic arithmetic
puts 1 + 2 * 3 # 7 puts (1 + 2) * 3 # 9
(display (+ 1 (* 2 3))) (newline) ; 7 (display (* (+ 1 2) 3)) (newline) ; 9
Prefix notation eliminates the need for operator precedence — the parentheses tell you exactly which operands go with which operator. +, -, *, and / all accept any number of arguments: (+ 1 2 3 4 5) returns 15. There is no "operator" / "function" distinction in Scheme — every + is just a function call.
Integer division and remainder
puts 17 / 5 # 3 puts 17 % 5 # 2 puts(-17 % 5) # 3 (Ruby uses floored division) puts 17.0 / 5 # 3.4
(display (quotient 17 5)) (newline) ; 3 (display (remainder 17 5)) (newline) ; 2 (display (modulo -17 5)) (newline) ; 3 — same sign as divisor (display (/ 17.0 5)) (newline) ; 3.4
Scheme separates integer division (quotient) from generic division (/) — there is no quietly different behavior depending on whether both operands are integers. remainder takes the sign of the dividend; modulo takes the sign of the divisor (matching Ruby's %). The distinction matters for negative numbers.
Exponents and roots
puts 2 ** 10 # 1024 puts Math.sqrt(2) # 1.4142135623730951
(display (expt 2 10)) (newline) ; 1024 (display (sqrt 2)) (newline) ; 1.4142135623730951
expt raises the first argument to the second — (expt 2 10) is the integer 1024, not a float. sqrt returns an exact answer when the argument is a perfect square and an inexact one otherwise. Scheme is unusual in distinguishing "exact" (arbitrary-precision rationals) from "inexact" (floats) at the type level.
Numeric predicates
puts 5.zero? # false puts 0.zero? # true puts 5.positive? # true puts(-3.negative?)# true puts 4.even? # true
(display (zero? 5)) (newline) ; #f (display (zero? 0)) (newline) ; #t (display (positive? 5)) (newline) ; #t (display (negative? -3)) (newline) ; #t (display (even? 4)) (newline) ; #t
Predicates conventionally end with ? — Scheme writes zero? where Ruby writes zero?, with identical meaning. The convention is older than Ruby. Boolean output is #t (true) and #f (false), not the words. Every value in Scheme except #f is truthy — including 0, '(), and "" — so Rubyists must remember 0 is not falsy here.
Integers, floats, and predicates
puts 7.is_a?(Integer) # true puts 3.14.is_a?(Float) # true puts (1.0 / 3).is_a?(Float) # true puts 7.class # Integer puts (1.0 / 3).round(3) # 0.333
(display (integer? 7)) (newline) ; #t (display (integer? 3.14)) (newline) ; #f (display (number? 3.14)) (newline) ; #t (display (real? 7)) (newline) ; #t (display (/ 1.0 3)) (newline) ; 0.3333333333333333
Standard Scheme defines a "numeric tower": integer?rational?real?complex?number?. BiwaScheme implements integers and IEEE-754 floats (the JavaScript way) — it does not support exact rationals or complex numbers, despite the predicates being present. A full Scheme like Chez or Racket would let you write 1/3 as an exact value; here, anything fractional becomes a float.
Booleans and Equality
Truthiness — only #f is false
puts(0 ? "truthy" : "falsy") # truthy puts("" ? "truthy" : "falsy") # truthy puts(nil ? "truthy" : "falsy") # falsy puts(false ? "truthy" : "falsy") # falsy
(display (if 0 "truthy" "falsy")) (newline) ; truthy (display (if "" "truthy" "falsy")) (newline) ; truthy (display (if '() "truthy" "falsy")) (newline) ; truthy — empty list is truthy! (display (if #f "truthy" "falsy")) (newline) ; falsy
In Scheme #f is the only false value — every other value, including 0, the empty string, and the empty list, is truthy. This trips up Rubyists who expect nil/false to both be falsy. Scheme has no nil: the empty list is '(), and the "no value" idiom is the unspecified value returned by set! or an empty begin.
and / or — short-circuit
puts(1 && 2 && 3) # 3 puts(false || nil || 5) # 5 puts !false # true
(display (and 1 2 3)) (newline) ; 3 — last truthy value (display (or #f #f 5)) (newline) ; 5 — first truthy value (display (not #f)) (newline) ; #t
and returns the last truthy value (or #f on the first false). or returns the first truthy value (or #f if none). These match Ruby's && and || exactly. not is the negation function — there is no ! sigil because every operator in Scheme is just a regular identifier.
eq? / eqv? / equal?
puts "abc".equal?("abc") # false (different object_ids) puts "abc".eql?("abc") # true (same type and value) puts "abc" == "abc" # true puts [1, 2] == [1, 2] # true puts :foo == :foo # true
(display (eq? 'foo 'foo)) (newline) ; #t — symbols are interned (display (eq? "abc" "abc")) (newline) ; #f — may or may not be same object (display (eqv? 1 1)) (newline) ; #t — numbers and chars (display (equal? '(1 2 3) '(1 2 3))) (newline) ; #t — recursive structural compare
Three flavors of equality, each cheaper than the last: eq? is pointer identity (fastest, only safe for symbols and small integers); eqv? handles numbers and characters correctly; equal? recurses into lists, vectors, and strings. Use equal? by default unless you have a reason not to — it is Ruby's ==.
Strings and Symbols
String construction and length
greeting = "Hello, " + "World!" puts greeting puts greeting.length
(define greeting (string-append "Hello, " "World!")) (display greeting) (newline) (display (string-length greeting)) (newline)
string-append joins any number of strings — + is only for numbers. string-length returns the character count. Scheme has no string interpolation: you build output piece by piece with display calls or with string-append. This is more verbose than Ruby's "#{...}", and it is one of the reasons Schemers write macros (see the macros section).
Substring extraction
line = "Hello, World!" puts line[0, 5] # "Hello" puts line[7..] # "World!" puts line[7..11] # "World"
(define line "Hello, World!") (display (substring line 0 5)) (newline) ; Hello (display (substring line 7 (string-length line))) (newline) ; World! (display (substring line 7 12)) (newline) ; World
substring takes a half-open range [start, end) — the end index is exclusive, like Ruby's .... There is no built-in "rest of string" syntax: pass (string-length line) explicitly. Indexing is zero-based.
Strings as lists of characters
puts "abc".chars.inspect # ["a", "b", "c"] puts "abc".chars.reverse.join # "cba"
(display (string->list "abc")) (newline) ; (a b c) (display (list->string (reverse (string->list "abc"))))(newline) ; cba
string->list and list->string convert between strings and lists of characters. Once a string is a list, all the list operations (reverse, map, filter) become available — this is the standard Scheme idiom for any non-trivial string manipulation. Characters write as #\a, #\space, #\newline.
Symbols and conversion
tag = :ok puts tag # ok puts tag.to_s # ok puts "ok".to_sym == tag # true
(define tag 'ok) (display tag) (newline) ; ok (display (symbol->string tag)) (newline) ; ok (display (eq? (string->symbol "ok") tag)) (newline) ; #t
Scheme symbols are written with a leading quote and are interned the same way Ruby symbols are — two symbols spelled the same are eq?, making them ideal for tags and dispatch keys. 'ok is shorthand for (quote ok); the quote suppresses evaluation, leaving the bare identifier as a value.
Number ⇄ string
puts "42".to_i # 42 puts Integer("0xff", 16) # 255 puts 255.to_s(16) # "ff" puts (1.0 / 4).to_s # "0.25"
(display (string->number "42")) (newline) ; 42 (display (string->number "ff" 16)) (newline) ; 255 (display (number->string 255 16)) (newline) ; ff (display (number->string (/ 1.0 4))) (newline) ; 0.25
string->number returns #f when the input is not a valid number — there is no exception to catch, just a falsy return value. Both directions take an optional radix argument (2, 8, 10, or 16). This is the conversion to reach for when reading or formatting numeric output.
Pairs and Lists
cons / car / cdr — the building blocks
pair = [1, 2] puts pair.first # 1 puts pair.last # 2 # Ruby has no dotted pair — arrays are flat. numbers = [1, 2, 3] puts numbers.first # 1 puts numbers.drop(1).inspect # [2, 3]
(define pair (cons 1 2)) (display pair) (newline) ; (1 . 2) — dotted pair (display (car pair)) (newline) ; 1 (display (cdr pair)) (newline) ; 2 (define numbers (list 1 2 3)) (display (car numbers)) (newline) ; 1 (display (cdr numbers)) (newline) ; (2 3)
Every list in Scheme is built from pairs: (1 2 3) is really (cons 1 (cons 2 (cons 3 '()))). car gets the first element of a pair, cdr ("could-er") gets the rest. The unusual names come from the original Lisp on the IBM 704 in 1958 — they are short for "Contents of Address Register" and "Contents of Decrement Register" of the underlying hardware.
Constructing lists
numbers = [1, 2, 3] shopping = ["bread", "milk", "eggs"] puts numbers.inspect puts shopping.inspect
(define numbers (list 1 2 3)) (define shopping (list "bread" "milk" "eggs")) (display numbers) (newline) ; (1 2 3) (display shopping) (newline) ; (bread milk eggs) (display '(1 2 3)) (newline) ; quote — literal list, same result (display `(1 ,(+ 1 1) 3)) (newline) ; quasiquote — splice in computed values
(list 1 2 3) calls the list function with three arguments. '(1 2 3) is the same thing but as a literal — the quote suppresses evaluation. ` (quasiquote) is a literal you can punch holes in with , (unquote), giving you the closest thing Scheme has to Ruby's "#{...}" string interpolation, but for data instead of strings.
length, append, reverse
numbers = [1, 2, 3, 4] puts numbers.length # 4 puts (numbers + [5, 6]).inspect # [1, 2, 3, 4, 5, 6] puts numbers.reverse.inspect # [4, 3, 2, 1]
(define numbers '(1 2 3 4)) (display (length numbers)) (newline) ; 4 (display (append numbers '(5 6))) (newline) ; (1 2 3 4 5 6) (display (reverse numbers)) (newline) ; (4 3 2 1)
length, append, and reverse are all O(n) — Scheme lists are singly linked, so the operations that are O(1) on Ruby arrays (random access, length) are O(n) here, and the operations that are O(n) on Ruby arrays (prepend) are O(1) here. Match the data structure to the access pattern.
Indexed access
letters = [:a, :b, :c, :d, :e] puts letters[2] # :c puts letters[2..].inspect # [:c, :d, :e]
(define letters '(a b c d e)) (display (list-ref letters 2)) (newline) ; c (display (list-tail letters 2)) (newline) ; (c d e)
list-ref walks the spine — it is O(n), not the O(1) you get from Ruby's []. list-tail returns the sublist starting at the given index, the equivalent of Ruby's letters[2..]. If you find yourself reaching for these often, a vector is probably the right data structure.
member and assoc — search
numbers = [1, 2, 3, 4, 5] idx = numbers.index(3) puts numbers[idx..].inspect # [3, 4, 5] ages = { alice: 30, bob: 25, carol: 35 } puts ages[:bob] # 25
(define numbers '(1 2 3 4 5)) (display (member 3 numbers)) (newline) ; (3 4 5) — the tail starting at the match (define ages '((alice . 30) (bob . 25) (carol . 35))) (display (cdr (assq 'bob ages))) (newline) ; 25
member returns the tail of the list starting at the first match (or #f if absent) — not just true/false. assoc (and the faster assq for symbol keys) returns the matching pair from an association list. Assoc lists are how a Schemer expresses what a Rubyist would reach for a Hash for — fine for small collections, slow for large ones (use hashtables for those).
Improper (dotted) lists
# Ruby has no dotted-pair concept — arrays are flat. # The closest analogue is a two-element array used as a key/value pair. pair = [:name, "Alice"] puts pair.inspect
(define proper '(1 2 3)) ; ends in '() (define improper '(1 2 . 3)) ; last cdr is 3, not '() (display proper) (newline) ; (1 2 3) (display improper) (newline) ; (1 2 . 3)
A proper list ends with the empty list '(); an improper list ends with anything else. The dot in (1 2 . 3) shows the final cdr explicitly. Improper lists appear most often as the result of (cons 'key 'value) — a single pair used as an entry in an assoc list. Most list functions (map, length) require proper lists and error on improper ones.
Higher-Order Functions
map — transform every element
numbers = [1, 2, 3, 4, 5] puts numbers.map { |number| number * number }.inspect # [1, 4, 9, 16, 25]
(display (map (lambda (number) (* number number)) '(1 2 3 4 5))) (newline) ; (1 4 9 16 25)
map in Scheme takes the function first and the list second — the opposite of Ruby's method-receiver order, but identical in meaning. lambda is the anonymous-function constructor; the symbol next to it is the parameter list. Functions are values: pass them, return them, store them in variables.
map across multiple lists
xs = [1, 2, 3] ys = [10, 20, 30] sums = xs.zip(ys).map { |x, y| x + y } puts sums.inspect # [11, 22, 33]
(display (map + '(1 2 3) '(10 20 30))) (newline) ; (11 22 33)
Scheme's map accepts any number of lists — the function is called with one argument per list. Ruby requires an explicit zip step. Note that + is just a function name here; there is no special "operators are different from functions" rule to work around.
filter — keep matching elements
numbers = [1, 2, 3, 4, 5, 6, 7, 8] puts numbers.select(&:even?).inspect # [2, 4, 6, 8]
(display (filter even? '(1 2 3 4 5 6 7 8))) (newline) ; (2 4 6 8)
filter keeps every element for which the predicate returns truthy. The predicate is just a function — here we pass even? by name. The result is always a list; the original is unchanged. Scheme has no in-place "destructive filter" by convention — operations that mutate end with !.
fold-left and fold-right
numbers = [1, 2, 3, 4, 5] puts numbers.reduce(0) { |sum, n| sum + n } # 15 puts numbers.reduce([]) { |acc, n| acc + [n * 2] }.inspect # [2, 4, 6, 8, 10]
(display (fold-left + 0 '(1 2 3 4 5))) (newline) ; 15 (display (fold-right cons '() '(1 2 3 4 5))) (newline) ; (1 2 3 4 5) (display (fold-right (lambda (n acc) (cons (* n 2) acc)) '() '(1 2 3 4 5))) (newline) ; (2 4 6 8 10)
fold-left processes left-to-right (accumulator first in the lambda), fold-right right-to-left (element first, accumulator last). When the operation is associative — like + — either works. For building lists, fold-right with cons preserves order naturally; fold-left would reverse it.
for-each — side effects only
["alice", "bob", "carol"].each { |name| puts name }
(for-each (lambda (name) (display name) (newline)) '("alice" "bob" "carol"))
for-each is the side-effect twin of map — it walks the list, calls the function on each element, and returns an unspecified value. Use it when you care about the work done (printing, mutating) rather than the result. Like map, it accepts any number of parallel lists.
apply — call with a list of arguments
numbers = [1, 2, 3, 4, 5] puts numbers.sum # 15 (or numbers.inject(:+)) puts [*numbers, 100].sum # 115
(define numbers '(1 2 3 4 5)) (display (apply + numbers)) (newline) ; 15 (display (apply + 100 numbers)) (newline) ; 115
apply calls a function with the elements of a list as separate arguments — Ruby's *splat at the call site. Extra arguments before the final list are prepended: (apply + 100 '(1 2 3)) is (+ 100 1 2 3). This is how to "unpack" a list back into positional arguments.
Control Flow
if is an expression
age = 18 status = if age >= 18 then "adult" else "minor" end puts status
(define age 18) (define status (if (>= age 18) "adult" "minor")) (display status) (newline)
if in Scheme is always an expression — it has a value, like Ruby's if. The shape is fixed: condition, then-branch, else-branch. There is no elsif; nest ifs or reach for cond. Omitting the else-branch is legal but the value is unspecified, so it is bad form unless you are using if purely for its side effect.
cond — multi-way branch
def grade(score) case when score >= 90 then "A" when score >= 80 then "B" when score >= 70 then "C" when score >= 60 then "D" else "F" end end puts grade(85)
(define (grade score) (cond ((>= score 90) "A") ((>= score 80) "B") ((>= score 70) "C") ((>= score 60) "D") (else "F"))) (display (grade 85)) (newline)
cond tries each clause in order and returns the body of the first whose test is truthy. else is a special identifier recognized only in the last clause. Ruby's case-without-subject is the closest analogue — same idea, different surface syntax.
case — dispatch on a value
def color_code(color) case color when :red then "#f00" when :green then "#0f0" when :blue then "#00f" else "unknown" end end puts color_code(:green)
(define (color-code color) (case color ((red) "#f00") ((green) "#0f0") ((blue) "#00f") (else "unknown"))) (display (color-code 'green)) (newline)
case compares its target against each list of candidates with eqv?. The candidates are wrapped in parentheses because multiple keys can share one body: ((red crimson scarlet) "#f00") matches three symbols. Ruby's case/when matches with === instead, which is much more flexible — and much more surprising.
when and unless — one-armed if
result = [] [1, 2, 3, 4, 5].each do |number| result << number if number.even? end puts result.inspect # [2, 4]
(define result '()) (for-each (lambda (number) (when (even? number) (set! result (cons number result)))) '(1 2 3 4 5)) (display (reverse result)) (newline) ; (2 4)
when evaluates its body if the test is truthy; unless does so if the test is falsy. Both wrap their body in an implicit begin, so they can hold multiple expressions without parentheses. Ruby uses them as trailing modifiers more often; in Scheme they are most idiomatic at the head of a block.
begin — sequence side effects
result = begin puts "step 1" puts "step 2" 42 end puts result # 42
(define result (begin (display "step 1") (newline) (display "step 2") (newline) 42)) (display result) (newline) ; 42
begin groups expressions and returns the value of the last one. It is the Scheme way to write "sequence of statements" inside a single-expression slot — for example, in the body of an if when you want to do two things. Many forms (when, lambda, let) wrap their body in an implicit begin.
do — explicit iteration
sum = 0 (1..10).each { |number| sum += number } puts sum # 55
(do ((counter 1 (+ counter 1)) (total 0 (+ total counter))) ((> counter 10) (display total) (newline))) ; 55
do is Scheme's only built-in looping form — every binding gets an initial value and a step expression, the loop runs while the test is false, and the body runs each iteration. It is rarely used in practice; idiomatic Scheme prefers map/fold/for-each or named-let recursion (see the recursion section).
Recursion and Tail Calls
Simple recursion
def factorial(n) return 1 if n.zero? n * factorial(n - 1) end puts factorial(10) # 3628800
(define (factorial n) (if (= n 0) 1 (* n (factorial (- n 1))))) (display (factorial 10)) (newline) ; 3628800
Recursion is the idiomatic loop in Scheme. This version is not tail-recursive: the multiplication happens after the recursive call returns, so each call adds a stack frame. For small inputs this is fine; for inputs in the millions, the next example shows the fix.
Tail-recursive accumulator
def factorial(n, accumulator = 1) return accumulator if n.zero? factorial(n - 1, n * accumulator) end puts factorial(10) # Note: Ruby does NOT guarantee tail-call optimization, # so deep recursion still overflows the stack.
(define (factorial n) (define (loop count accumulator) (if (= count 0) accumulator (loop (- count 1) (* count accumulator)))) (loop n 1)) (display (factorial 10)) (newline) ; 3628800
When the recursive call is the last action in the function — nothing wraps it — Scheme is required by the standard to reuse the stack frame. This is "proper tail-call optimization": this loop runs in constant stack space, no matter how deep. Calling (factorial 1000000) would not overflow. Ruby has no such guarantee.
Named let — local recursion
def fibonacci(target) i = 0; a = 0; b = 1 while i < target a, b = b, a + b i += 1 end a end puts fibonacci(20) # 6765
(define (fibonacci target) (let loop ((counter 0) (a 0) (b 1)) (if (= counter target) a (loop (+ counter 1) b (+ a b))))) (display (fibonacci 20)) (newline) ; 6765
Named let defines and immediately calls a local recursive function. The name (loop here) is in scope only inside the body, so it cannot pollute the enclosing scope. This is the idiomatic Scheme replacement for while and for loops — declare the state variables in the binding list, recurse with new values.
Mutual recursion
def is_even?(n) = n.zero? ? true : is_odd?(n - 1) def is_odd?(n) = n.zero? ? false : is_even?(n - 1) puts is_even?(100) # true
(define (is-even? n) (if (= n 0) #t (is-odd? (- n 1)))) (define (is-odd? n) (if (= n 0) #f (is-even? (- n 1)))) (display (is-even? 100)) (newline) ; #t
Two top-level defines can refer to each other because both names exist in the same scope by the time either body is called. Inside a let, use letrec instead. Because Scheme guarantees tail-call optimization, mutually-recursive functions like this run in constant stack space — calling (is-even? 1000000) would work.
Lambdas and Closures
lambda — anonymous functions
add = ->(a, b) { a + b } puts add.call(2, 3) # 5 puts add.(2, 3) # 5 (alternative call syntax)
(define add (lambda (a b) (+ a b))) (display (add 2 3)) (newline) ; 5
lambda creates an unnamed function. Unlike Ruby, Scheme has no separate concept of a "block" — functions are the blocks. (define (add a b) ...) is just shorthand for (define add (lambda (a b) ...)). Every function in Scheme is a first-class value: pass it, return it, store it.
Variable-arity with dot
def sum_all(*numbers) numbers.sum end puts sum_all(1, 2, 3, 4, 5) # 15 def head_and_rest(first, *rest) puts "first: #{first}, rest: #{rest.inspect}" end head_and_rest(1, 2, 3, 4, 5)
(define (sum-all . numbers) (apply + numbers)) (display (sum-all 1 2 3 4 5)) (newline) ; 15 (define (head-and-rest first . rest) (display "first: ") (display first) (display ", rest: ") (display rest) (newline)) (head-and-rest 1 2 3 4 5)
The dot in a parameter list collects all remaining arguments into a list — Ruby's *splat in argument position. (lambda args ...) with no parentheses around args collects every argument into one list. There are no default values or keyword arguments in standard Scheme; these are conventionally simulated by checking the rest list.
Closures over local state
def make_counter count = 0 -> { count += 1 } end increment = make_counter puts increment.call # 1 puts increment.call # 2 puts increment.call # 3
(define (make-counter) (let ((count 0)) (lambda () (set! count (+ count 1)) count))) (define increment (make-counter)) (display (increment)) (display " ") (display (increment)) (display " ") (display (increment)) (newline) ; 1 2 3
The inner lambda captures the count binding from its enclosing let. Each call to make-counter creates a fresh binding, so independent counters do not interfere. This is the lexical-scope + closure model that Ruby, Python, and JavaScript all imitate — Scheme was where it was popularized in 1975.
Functions that return functions
def adder(amount) ->(number) { number + amount } end add_five = adder(5) puts add_five.call(10) # 15 puts add_five.call(100) # 105
(define (adder amount) (lambda (number) (+ number amount))) (define add-five (adder 5)) (display (add-five 10)) (newline) ; 15 (display (add-five 100)) (newline) ; 105
Currying-by-hand is trivial in Scheme — adder takes one argument and returns a one-argument function. Because functions are values and closures capture their environment, this pattern is everywhere in idiomatic Scheme: configuration, partial application, building pipelines.
Vectors and Hashtables
Vectors — fixed-size arrays
numbers = [10, 20, 30, 40, 50] puts numbers[2] # 30 numbers[2] = 999 puts numbers.inspect # [10, 20, 999, 40, 50] puts numbers.length # 5
(define numbers (vector 10 20 30 40 50)) (display (vector-ref numbers 2)) (newline) ; 30 (vector-set! numbers 2 999) (display numbers) (newline) ; #(10 20 999 40 50) (display (vector-length numbers))(newline) ; 5
Vectors are Scheme's contiguous, mutable, O(1)-access arrays — the right structure when you need random access. The literal syntax is #(1 2 3); the constructor is vector. Unlike lists, vectors are not built from pairs and do not work with car/cdr. The trailing exclamation in vector-set! signals mutation.
Vector operations
numbers = [1, 2, 3, 4, 5] squares = numbers.map { |number| number * number } puts squares.inspect # [1, 4, 9, 16, 25] puts numbers.to_a.inspect # [1, 2, 3, 4, 5]
(define numbers #(1 2 3 4 5)) (define squares (vector-map (lambda (number) (* number number)) numbers)) (display squares) (newline) ; #(1 4 9 16 25) (display (vector->list numbers)) (newline) ; (1 2 3 4 5) (display (list->vector '(a b c))) (newline) ; #(a b c)
vector-map mirrors map but operates on (and returns) vectors. vector->list and list->vector bridge the two worlds — useful when a function you want lives on the other side of that divide. For random-access workloads, store data in a vector and convert to a list only when chaining list operations becomes more convenient.
Hashtables (R6RS)
ages = { "alice" => 30, "bob" => 25, "carol" => 35 } ages["dave"] = 40 puts ages["bob"] # 25 puts ages.key?("eve") # false puts ages.keys.sort.inspect # ["alice", "bob", "carol", "dave"]
(define ages (make-hashtable string-hash string=?)) (hashtable-set! ages "alice" 30) (hashtable-set! ages "bob" 25) (hashtable-set! ages "carol" 35) (hashtable-set! ages "dave" 40) (display (hashtable-ref ages "bob" #f)) (newline) ; 25 (display (hashtable-contains? ages "eve")) (newline) ; #f (display (vector->list (hashtable-keys ages))) (newline) ; (alice bob carol dave) — order is unspecified
BiwaScheme ships R6RS hashtables: make-eq-hashtable for symbol keys, make-eqv-hashtable for numbers/characters, and the general make-hashtable that takes a hash function and an equality predicate. hashtable-ref takes a third argument: the default to return on a miss (here #f) — there is no exception to catch.
Hashtable with symbol keys
config = { host: "localhost", port: 8080, debug: true } config.each { |key, value| puts "#{key}=#{value}" }
(define config (make-eq-hashtable)) (hashtable-set! config 'host "localhost") (hashtable-set! config 'port 8080) (hashtable-set! config 'debug #t) (for-each (lambda (key) (display key) (display "=") (display (hashtable-ref config key #f)) (newline)) (vector->list (hashtable-keys config)))
make-eq-hashtable is the cheapest variant — it uses pointer identity (eq?) for comparison, perfect for symbol keys because all symbols spelled the same are eq?. There is no built-in hashtable-walk with a callback; iterate by walking the keys vector.
Records
define-record-type
Point = Struct.new(:x, :y) origin = Point.new(0, 0) puts origin.x # 0 puts origin.y # 0 puts origin.class # Point
(define-record-type point (fields x y)) (define origin (make-point 0 0)) (display (point-x origin)) (newline) ; 0 (display (point-y origin)) (newline) ; 0 (display (point? origin)) (newline) ; #t
define-record-type generates a constructor (make-point), a predicate (point?), and one accessor per field (point-x, point-y). The generated names follow strict conventions — there is no way to ask for camelCase. Records are the Scheme equivalent of Ruby's Struct or a no-method class.
Mutable fields
class Counter attr_accessor :tally def initialize = (@tally = 0) end counter = Counter.new counter.tally += 1 counter.tally += 1 puts counter.tally # 2
(define-record-type counter (fields (mutable tally))) (define c (make-counter 0)) (counter-tally-set! c (+ (counter-tally c) 1)) (counter-tally-set! c (+ (counter-tally c) 1)) (display (counter-tally c)) (newline) ; 2
Fields are immutable by default — mark them (mutable name) to get an additional setter procedure named typename-fieldname-set!. The immutable-by-default stance is the opposite of Ruby's attr_accessor idiom and reflects Scheme's preference for value-style programming.
Custom constructor and predicate names
# Ruby's Struct already lets you customize names; the closest equivalent is # a regular class with explicit named accessors: class Animal attr_reader :name attr_accessor :age def initialize(name, age) = (@name, @age = name, age) end rex = Animal.new("Rex", 3) puts rex.name puts rex.age
(define-record-type (animal new-animal animal?) (fields (immutable name) (mutable age))) (define rex (new-animal "Rex" 3)) (display (animal-name rex)) (newline) ; Rex (display (animal-age rex)) (newline) ; 3 (animal-age-set! rex 4) (display (animal-age rex)) (newline) ; 4
The (name constructor predicate) form lets you override the default constructor name (make-animalnew-animal) and the predicate. Accessors and setters still follow the type-field / type-field-set! pattern. Marking a field immutable simply omits the setter.
Macros
define-macro — quasiquote templates
# Ruby has no macro system. A method that takes a block is the # nearest analogue: it gets the body as code but cannot rewrite it. def my_when(condition, &block) block.call if condition end my_when(true) { puts "hello" }
(define-macro (my-when test . body) `(if ,test (begin ,@body) (begin))) (my-when #t (display "hello") (newline) (display "world") (newline))
A macro is a function that takes code as data (a list) and returns new code as data — the result is then compiled in place of the call. ` (quasiquote) builds a list with holes, , (unquote) plugs a value in, and ,@ (unquote-splicing) plugs a list in flat. The body of my-when after macro expansion is literally (if #t (begin (display "hello") (newline) ...) (begin)).
Macros control evaluation
# Cannot be written in Ruby without metaprogramming: # functions evaluate their arguments before being called, so a # function-shaped "swap!" cannot reassign the caller's locals. def swap_via_array(pair) pair[0], pair[1] = pair[1], pair[0] end pair = [1, 2] swap_via_array(pair) puts pair.inspect # [2, 1]
(define-macro (swap! a b) `(let ((temporary ,a)) (set! ,a ,b) (set! ,b temporary))) (define x 1) (define y 2) (swap! x y) (display x) (display " ") (display y) (newline) ; 2 1
Macros run before evaluation, so they can introduce set! on names the caller passed in. A function-shaped swap! cannot do this — by the time the function runs, it sees only the values, not the variables. This is the killer feature that gives Lisp dialects their famous "language extensibility": new control structures look identical to built-ins.
Building new control structures
# Ruby already ships unless, but if it didn't, you'd # write something like: def my_unless(condition, &block) block.call unless condition end my_unless(false) { puts "ran" }
(define-macro (my-unless test . body) `(if ,test (begin) (begin ,@body))) (my-unless #f (display "ran") (newline))
Defining a brand-new conditional that looks built-in takes one line. With macros, you do not have to wait for the language committee — you ship the missing form yourself. BiwaScheme caveat: the R7RS-standard pattern-based syntax-rules is not implemented in BiwaScheme 0.8.3. The lower-level define-macro (CL-style) is what works here.
Hygiene caveat
# Ruby's metaprogramming has the same hazard — a method # generating method code can accidentally collide with names # used at the call site. There's no automatic hygiene.
; The temporary name "temporary" used inside swap! could collide ; with a caller's variable of the same name. Hand-written ; define-macro is unhygienic — the macro writer must choose ; unique names. R7RS syntax-rules would make this safe ; automatically, but BiwaScheme does not yet implement it. (define-macro (with-pair pair body) `(let ((first (car ,pair)) (rest (cdr ,pair))) ,body)) (display (with-pair '(10 20 30) (+ first (car rest)))) (newline) ; 30
Unhygienic macros — the kind define-macro produces — can capture or shadow names from the call site. The fix in standard Scheme is syntax-rules, a hygienic, pattern-based macro form that automatically renames temporaries. When BiwaScheme adds syntax-rules support, every macro on this page becomes more robust.
Continuations (call/cc)
call/cc as escape
def first_negative(numbers) numbers.each { |number| return number if number.negative? } nil end puts first_negative([1, 2, 3, -4, 5]) # -4 puts first_negative([1, 2, 3, 4, 5]).inspect # nil
(define (first-negative numbers) (call/cc (lambda (return) (for-each (lambda (number) (if (negative? number) (return number))) numbers) #f))) (display (first-negative '(1 2 3 -4 5))) (newline) ; -4 (display (first-negative '(1 2 3 4 5))) (newline) ; #f
call/cc (call-with-current-continuation) captures "the rest of the computation" as a callable value. Invoking it abandons the current work and jumps back to where call/cc was. Used this way it is a structured non-local return — like Ruby's return or throw/catch, but more general because the continuation is a first-class value you can also stash and call later.
Continuations as resumable points
# Ruby has Fiber and Enumerator::Lazy, which give coroutine-like # behavior, but full first-class re-invocable continuations are # not available. Fibers can resume once-per-yield, not arbitrarily # many times the way a Scheme continuation can.
(define saved-continuation #f) (display "result: ") (display (+ 1 (call/cc (lambda (k) (set! saved-continuation k) 10)))) (newline) ; first time: prints "result: 11" ; saved-continuation now points back into the (+ 1 _) expression. ; In a fresh BiwaScheme session you could call it again with a ; different value, like (saved-continuation 100), to resume there.
A captured continuation can be invoked any number of times, jumping back to the same spot with a different value each time. Used carelessly this resembles a goto that respects lexical scope; used carefully it is the foundation for coroutines, generators, backtracking search, and exception systems — all of which can be implemented in Scheme without further language support.
dynamic-wind — safe cleanup
def with_resource puts "acquire" yield ensure puts "release" end with_resource { puts "using" }
(dynamic-wind (lambda () (display "acquire") (newline)) ; before (lambda () (display "using") (newline)) ; thunk (lambda () (display "release") (newline))) ; after
dynamic-wind runs three thunks: the before thunk before the body, the after thunk after — and crucially, the after thunk also runs if a continuation jumps out of the body (and the before thunk runs again if a continuation jumps back in). This is the Scheme analogue of Ruby's ensure — and it stays correct even in the presence of call/cc.
I/O and String Ports
Building strings with output ports
output = String.new output << "name=" output << "alice" output << " age=" output << 30.to_s puts output # name=alice age=30
(define output (open-output-string)) (display "name=" output) (display "alice" output) (display " age=" output) (display 30 output) (display (get-output-string output)) (newline) ; name=alice age=30
Scheme has no Ruby-style mutable string buffer. The idiomatic way to build a string is to open a string-output port, write to it with the normal display/write procedures (passing the port as the second argument), then call get-output-string to harvest the result. Every output procedure takes an optional port argument that defaults to stdout.
Capturing formatted output
def render(numbers) result = String.new result << "Total: " result << numbers.sum.to_s result << " (avg " result << (numbers.sum.to_f / numbers.length).round(2).to_s result << ")" result end puts render([1, 2, 3, 4, 5]) # Total: 15 (avg 3.0)
(define (render numbers) (let ((port (open-output-string))) (display "Total: " port) (display (apply + numbers) port) (display " (avg " port) (display (/ (apply + numbers) (length numbers) 1.0) port) (display ")" port) (get-output-string port))) (display (render '(1 2 3 4 5))) (newline) ; Total: 15 (avg 3)
Returning a freshly-built string from a function is a common Scheme pattern: open a port locally, write into it, return (get-output-string port). Because the port is local to the function, callers see only the finished string and never the intermediate state. The trailing 1.0 in the average computation forces a floating-point result — any operation that involves a float yields a float, a rule called "inexactness contagion".
There is no printf
name = "Alice" score = 87.5 puts "%-10s scored %5.1f" % [name, score] # Alice scored 87.5
(define (pad-right text width) (let ((extra (- width (string-length text)))) (if (> extra 0) (string-append text (make-string extra #\space)) text))) (define name "Alice") (define score 87.5) (display (pad-right name 10)) (display " scored ") (display score) (newline) ; Alice scored 87.5
Standard Scheme has no printf/format in the small base — formatted output is built from primitives. Many implementations ship a format SRFI as an extension, but BiwaScheme does not. The result is more verbose code, but also more visible: nothing about the output is hidden in a cryptic format string.
Gotchas for Rubyists
0 and '' are truthy
puts(0 ? "in" : "out") # in puts("" ? "in" : "out") # in puts(nil ? "in" : "out") # out
(display (if 0 "in" "out")) (newline) ; in (display (if "" "in" "out")) (newline) ; in (display (if #f "in" "out")) (newline) ; out
#f is the only false value. 0, "", '(), and even (if #f #t)'s unspecified value all count as truthy. Coming from Ruby, the trap is writing (if (assq 'key data) ...) expecting it to be falsy when missing — that part works, because assq returns #f on miss — but expecting 0 or '() to behave like Ruby's nil will burn you.
Quote captures, list evaluates
# Ruby always evaluates: there's no "literal data" syntax for # code-shaped values. x = 5 inline = [1, 2, x] # [1, 2, 5] puts inline.inspect
(define x 5) (display '(1 2 x)) (newline) ; (1 2 x) — symbol x, not 5 (display (list 1 2 x)) (newline) ; (1 2 5) — x evaluated (display `(1 2 ,x)) (newline) ; (1 2 5) — quasiquote with unquote
'(1 2 x) is a literal: every element is taken as-is. (list 1 2 x) calls list on three arguments, evaluating x. The mistake is reaching for the quote when you wanted construction — your symbol shows up where a value should be. Quasiquote (`) plus unquote (,) gives you a literal you can punch holes in.
eq? on strings does not work
puts "abc".equal?("abc") # false (different objects) puts "abc".eql?("abc") # true puts "abc" == "abc" # true
(display (eq? "abc" "abc")) (newline) ; #f or #t — unspecified (display (equal? "abc" "abc")) (newline) ; #t
eq? is pointer identity and is only reliable for symbols (which are interned) and small integers. Using eq? on strings or lists is undefined — sometimes the compiler shares a literal and you get #t, sometimes not. Use equal? for content comparison by default; reach for eq? only when you know you are comparing symbols or pointer-identity tags.
set! mutates, define rebinds
# In Ruby, x = 5 just reassigns x — there's no difference # between "first assignment" and "later assignment". x = 5 x = 10 puts x # 10
(define x 5) (define x 10) ; legal at top level, treated as set! (display x) (newline) ; 10 (let ((y 1)) (set! y 2) ; mutate ; (define y 3) ; illegal — define cannot appear here once expressions started (display y) (newline))
define creates a new binding; set! mutates an existing one. At the top level the two are often interchangeable, but inside a body all defines must come before any expressions — once the body has started executing, only set! can change bindings. Rubyists used to treating = as universal reassignment have to learn the distinction.

Lists are not arrays
numbers = [1, 2, 3, 4, 5] puts numbers[2] # 30 — O(1) access puts numbers.length # 5 — O(1) length puts numbers.last # 5 — O(1) on Ruby arrays
(define numbers '(1 2 3 4 5)) (display (list-ref numbers 2)) (newline) ; 3 — O(n) walk (display (length numbers)) (newline) ; 5 — O(n) walk (define (last-of items) (if (null? (cdr items)) (car items) (last-of (cdr items)))) (display (last-of numbers)) (newline) ; 5 — O(n)
Scheme lists are linked lists of pairs — every "operation that looks like array access" is actually a walk. Random access, length, and "last element" are all O(n). For workloads that need O(1) access, use vectors. For workloads that prepend often (the natural list operation), lists are faster than vectors because cons is O(1).