Ruby is a denser functional language than LISP
Added 31 Jul 2008
A dense language lets you say things concisely, without obfuscation. You can see more of your program in one glance, and there aren’t as many places for bugs to hide. Beyond a certain point, the only way to make programs denser is to use more powerful abstractions.
One particularly powerful abstraction is lambda. Using lambda, you can
create a new function on the fly, pass it to other functions, and even
store it for later use. For example, if you wanted to double each number
in a list, you might write:
(mapcar (lambda (n) (* n 2)) mylist)mapcar creates a new list by transforming each element of mylist. The
transformation, in this case, could be read as “for each value n,
multiply n by two.” In JavaScript, you’d write lambda as function,
which is perhaps a bit clearer:
map(function (n) { return n*2 }, mylist)Of course, this is only a hint of what you can do with lambda. Languages
which favor this style of programming are called functional languages,
because they work with functions. A dense functional language can be very
concise indeed, and quite clear once you learn to read it.
How does Ruby stack up against LISP for functional programming? Let’s consider Paul Graham’s canonical example, a function which creates an accumulator:
(defun foo (n) (lambda (i) (incf n i)))This code is marginally shorter in Ruby, and the notation will be more familiar to C hackers:
def foo(n) lambda {|i| n+=i} end
acc = foo 3
acc.call(1) # --> 4
acc.call(10) # --> 14
acc.call(0) # --> 14But there’s an interesting special case in Ruby which saves us even more
typing. Consider a (very silly) function which takes a lambda as an
argument:
;; Call 'fn' once for each natural number.
(defun each-natural-number (fn)
(loop for n from 1 do (funcall fn n)))
;; Print 1, 2, 3...
(each-natural-number
(lambda (n) (format t "~D~%" n)))Now, we could write the same function in Ruby:
def each_natural_number(fn)
n = 0
loop { fn.call(n += 1) }
end
each_natural_number(lambda {|n| puts n })But we can do better. Let’s get rid of lambda and fn using yield:
def each_natural_number
n = 0
loop { yield n += 1 }
end
each_natural_number {|n| puts n }Yes, yield is a special-purpose hack, and yes, it only works for functions
which take a single lambda. But in heavily functional code, yield
buys us a lot. Compare:
[1,2,3].map {|n| n*n }.reject {|n| n%3==1 }(remove-if (lambda (n) (= (mod n 3) 1))
(mapcar (lambda (n) (* n n))
'(1 2 3)))In a large program, the difference adds up. (In LISP’s defense, it’s
possible to write a reader macro which makes lambda more concise. But
this is rarely done.)