A popular game on comp.lang.lisp is comparing Lisp with Python. Lispers complain that Python has a crippled lambda and no lexical closures, and they hiss and boo whenever Python’s development tends in a non-functional direction.
I’ve recently been playing with Ruby. Lo and behold, it has real lambdas, closures, and a generally more functional style. The syntax is almost as clean as Python’s, without that significant whitespace that raises so many hackles.
One thing I don’t yet understand about Ruby is the way it treats functional arguments. A function or method in Ruby can have only one functional argument, which must be the last argument. Functional arguments, or “blocks,” as they’re called in Ruby, appear outside the argument list of the function when it is called. So it looks like this for short blocks:
object.method(arg1, arg2) { |var| block code }
Or like this for multi-line blocks:
object.method(arg1, arg2) do |var| block code . . . end
In both examples, |var|
is the block’s own argument list, so the Lispy equivalent of the above would be:
(method object arg1 arg2 (lambda (var) . . . ))
The main advantage to this syntax comes with iterators, which I take it were the reason Ruby got blocks in the first place. Ruby’s iterators are just methods of container objects that take a block as their only argument. Then one can omit the parentheses to get something like:
list.each do |item| do stuff with item end
This looks a bit like Smalltalk, one of Ruby’s inspirations. The Smalltalk approach has the advantage that most control constructs are just object methods, so they can be extended and modified as needed. (Unlike Smalltalk, Ruby has a few primitive controls that are not methods, such as if
.)
So the special syntax for blocks isn’t strictly necessary, but it does make the most common use for blocks syntactically very simple. In short, a compromise. Ruby also provides the Proc
class when general-purpose lambdas are needed.
I won’t advocate Lisp, Ruby, or Python over any other language; each has its place. But Lispers looking for a compromise with Python may want to give Ruby a spin.
I’m not sure, but I think the functional argument has to be the last argument to a function only if you want to use the special iterator-style block syntax with that function, which uses yield.
If you just want to pass method A to method B and call method A, I think A.call is good enough.
It does seem like Ruby did away with some of the Python annoyances.
“A function or method in Ruby can have only one functional argument, which must be the last argument. Functional arguments, or “blocks,” as they’re called in Ruby, appear outside the argument list of the function when it is called.”
A block is not the same thing as a functional argument. Your statement is false:
def meth( a, b )
puts a.call( 4 )
puts b.call( 4 )
end
meth( lambda { |a| a }, lambda { |a| a + 2 } )
Ruby has been a “gateway” to Lisp for some of my friends who wouldn’t even look at Lisp before. So you could title the article “Ruby is a Gateway to Lisp Programming”. ;o)
Ruby’s blocks are more than simple lambda’s, since they support control statements like “break”, “next”, etc.
Of course, you could create a sufficiently powerful macro that would handle these properly. Or you could use enough levels of blocks and return-from. It’s just not as trivial as a first glance might make it appear.
Stuart,
actually Ruby methods can be passed any number of lambdas:
foo = proc {|a| … }
bar = proc {|b| … }
doSomething(foo, bar)
(“proc” is even aliased to “lambda”)
The form:
doSomething {|c|
…
}
…is syntactical sugar on the outer side of doSomething for when you need just one lambda, and enables some syntactical sugar on the inner side: it lets you pass control and data to the block with “yield x” instead of “block.call(x)”.
I got interested in Lisp (and eventually fell in love with it) while programming in Ruby. I really hope the two communities will be friendly to each other because they have so much in common.
Very interesting post and comments. I believe that Ruby and Lisp have a lot in common because Matz was influenced by Lisp (in addition to Smalltalk and Perl).
I can see how Lisp programmers might appreciate Ruby a bit more than Python, though I’m sure there’s people who go the other way as well.
The bummer for me is that Ruby, Python, and Lisp are all very interesting languages, but I don’t get a lot of opportunities to play with them.