Lisp's Influence on Ruby

Ruby is often praised for its elegant, object-oriented syntax, but its most beloved features actually stem from Lisp. This article explores how Lisp's functional concepts shaped Ruby's blocks, symbols, and collection methods.
Once I wrote users.select { |u| u.admin? }.map(&:email)
and realized I’d written Lisp.
Not literally. The parentheses are gone, the prefix notation is gone, the lambdas are syntactic blocks. But the shape of the code (chain a filter onto a transform, ask each element a yes-or-no question with ?
, build the result without mutating anything) is Lisp. Ruby just put it in business casual.
Matz has said as much. He’s described Ruby’s design as starting from a simple Lisp, stripping out macros and s-expressions, then adding an object system, blocks, and Smalltalk-style methods. The features most Rubyists fall in love with aren’t the object-oriented ones. They’re the functional ones, dressed in friendlier clothes.
Here is the list I think about often, and why each one matters.
Method names with question marks
The convention that predicates end in ?
came from Scheme. zero?
, nil?
, empty?
, respond_to?
, valid?
. The mark tells you, at a glance, that the method answers a yes-or-no question. It does not mutate. It does not perform an action. It tells you something true or false about the receiver.
return if user.nil?
return unless user.admin?
notify(user) if user.subscribed?
You can read those three lines as English because the ?
? does the heavy lifting. The same convention shows up as !
for methods that mutate or raise: save!
, sort!
, compact!
. Both marks come from Scheme, where null?
, pair?
, and set!
work the same way.
A small syntactic borrow, but it threads through the whole language. Reading Ruby is faster because of those two characters.
Closures and blocks
Blocks are the feature most Rubyists name first when asked what they love about the language. They’re closures: chunks of code that capture their surrounding scope and can be passed around as values.
total = 0
[1, 2, 3].each { |n| total += n }
total # => 6
The block closes over total
. That is the closure pattern: a function value that remembers the environment it was defined in. Lisp had closures decades before Ruby. Scheme made them first-class objects you could pass to anything. Ruby kept the idea and added the lighter syntax. A block, with do…end
or curly braces, is a closure with the parentheses stripped off.
Procs and lambdas are the same idea with the parentheses back on:
square = ->(n) { n * n }
[1, 2, 3].map(&square) # => [1, 4, 9]
That arrow syntax is Ruby’s lambda
. The word itself is Lisp’s, from Church’s lambda calculus, plumbed into a working programming language for the first time in 1958.
First-class functions
Once you can name a closure and pass it around, functions become values. You can store them in arrays, return them from methods, attach them to objects. Ruby’s Method
and Proc
classes make this explicit. So does &:method_name
, which converts a symbol into a block by looking up the method on the receiver.
emails = users.map(&:email)
admins = users.select(&:admin?)
That &:foo
is a small piece of magic, and it works because functions are values in Ruby. The symbol gets coerced into a proc, the proc gets passed as a block, the block gets called on each element. First-class functions all the way down.
This is Lisp’s foundational idea: programs are built by composing functions. Ruby borrows the composition and dresses it up in dot-chains.
Symbols
:foo
is a symbol. It looks like a string with a colon, but it’s a different kind of value. Symbols are interned: every time you write :foo
, you get the same object. Two strings that look the same are usually two separate objects in memory; two symbols that look the same are always one.
That property comes from Lisp. Lisp symbols (atoms, in some dialects) are the original interned values. The reader sees foo
, looks it up in a symbol table, and either returns the existing symbol or creates a new one and remembers it. After that, all references to foo
point to the same object.
:status.equal?(:status) # => true
"status".equal?("status") # => false
What it buys you in Ruby: fast comparison, free hashing, and a clean syntax for names that aren’t strings.
config = { host: "localhost", port: 5432, ssl: true }
config[:host]
Hash keys are the obvious case, but the deeper use is method names. method_name
and :method_name
are the same idea at two levels. send(:save)
calls the save
method. define_method(:fetch) {…}
defines one. respond_to?(:to_s)
asks if one exists. Symbols are how Ruby refers to methods reflectively, which is how the metaprogramming works.
The &:foo
shortcut from the last section is the same idea on a closer pass: a symbol naming a method, coerced into a callable. Symbols carry the names; Ruby looks them up.
Collection methods
map
, select
, reject
, reduce
, each
, flat_map
, zip
, partition
, chunk_while
. The Enumerable
module is the part of Ruby I would miss most if I had to leave. It’s also the part most directly descended from Lisp.
Lisp gave us mapcar
, filter
, reduce
. The shape is the same: take a collection, apply a function, get a collection back. No indices. No off-by-ones. No accumulator variable to forget to reset.
orders
.select { |o| o.placed_at > 1.week.ago }
.group_by(&:customer_id)
.transform_values { |group| group.sum(&:total) }
That snippet would be five for-loops and a hash in a less expressive language. In Ruby it’s a paragraph that reads top-to-bottom. The chain is doing the same thing a series of nested Lisp maps and reduces would do; the syntax is dotted instead of parenthesized.
When Rubyists say “the language reads like English,” what they usually mean is “the collection methods compose into sentences.” That’s Lisp’s gift, with Ruby’s punctuation.
Lazy enumerators
Eager collection methods build the whole result, then return it. [1, 2, 3].map { |n| n *2 }
allocates a new array, fills it, hands it back. Fine for small lists. For large or infinite ones it’s a problem.
Lisp solved this with lazy evaluation and streams. Scheme’s delay
and force
, Clojure’s lazy sequences, Haskell’s everything. The idea: don’t compute the result until someone asks for it. A list isn’t an array sitting in memory; it’s a recipe for producing one element at a time.
Ruby has the same trick. Enumerable#lazy
returns an enumerator that pipes operations together without materializing the intermediate collections.
(1..Float::INFINITY)
.lazy
.select { |n| n % 3 == 0 }
.map { |n| n * n }
.first(5)
=> [9, 36, 81, 144, 225]
That pipeline reads from an infinite range. Without lazy
, the select
would try to scan the whole range before passing it on; the program would never finish. With lazy
, each value flows through the chain one at a time, and only five of them are ever computed.
The mechanics are pure Lisp. A lazy enumerator is a closure over the source plus a transformation. Calling next
advances the closure by one step. first(5)
calls next
five times, then stops. Everything else stays uncomputed.
You don’t reach for it often. When you do (paging through a large file, generating combinations until you find one that fits, walking a tree without flattening it), there’s nothing else in Ruby that does the job as cleanly.
Duck typing
If it walks like a duck and quacks like a duck, treat it like a duck. Don’t check its type. Send it the message and see what happens.
Smalltalk shares the credit here. Smalltalk’s “send any message to any object” is closer to duck typing than Lisp’s typed-but-dynamic approach. But Lisp’s tradition of dynamic typing, where values know their types and variables don’t, is part of the same lineage. The idea that a function should care about behavior, not class, runs through both.
def render(thing)
thing.to_s
end
That method works for a
Source: Hacker News











