Free since 2005 · No login required
AT

Academic Tutorials

Learn at your own pace

site-mobile-top-banner · 320x50

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) # --> 14

But 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.)