Can you do Lisp-like macros in Ruby?


#1

I’ve never used lisp before until ~2am last night…and while working
on the very simple unit testing framework in Practical Common Lisp [1]
I came up with this macro:

(defun report-result (result form)
(format t “~:[FAIL~;pass~] … ~a~%” result form))

(defmacro test (test-form)
(let ((result (gensym)))
`(let ((,result ,test-form))
(report-result ,result ',test-form)
,result)))

Using that macro, I can do:
CL-USER> (test (= (+ 1 2) 3))
pass … (= (+ 1 2) 3)
T

I really like the syntax of that. How could I do the same thing in
Ruby? I took a quick shot and missed…it might be super easy and I’m
just dumb from this apparent insomnia. I really like using Ruby, but
I would absolutely love it if I could build syntax like I did with
Lisp.

The Ruby translation of the syntax - not the code that actually makes
it work - would be like

irb> test { (1 + 2) == 3 }
pass … (1 + 2) == 3

Okay here’s what I’ve come up with:

def report_result(result, &block)
message = result ? “pass” : “FAIL”
puts “#{message} … #{block}”
end

def test(&block)
result = yield
report_result result, &block
result
end

irb> test { (1 + 2) == 3 }
pass … #Proc:0x00306b58@:55(irb)

So I guess the question is how to make the code show up.

Here’s my change:

def report_result(result, code)
message = result ? “pass” : “FAIL”
puts “#{message} … #{code}”
end

def test(code)
result = eval code
report_result result, code
result
end

irb> test “(1 + 2) == 3”
pass … (1 + 2) == 3

I certainly don’t like sticking my code in a string like that. That
just kind of sucks.

Over all this is kind of a lame example anyway. Just after spending
some time with Lisp though, I get the feeling that I’ll be able to do
some really cool stuff with it, stuff that I don’t know how to do in
Ruby, despite using Ruby for several months now. I don’t know exactly
what it is, but somehow I know that - at least with the Ruby coding
style I used above - I won’t be able to do some things with Ruby that
I would be able to in Lisp. I appreciate any feedback…hopefully you
guys don’t think I’m out in left field with this.

Pat

[1] http://www.gigamonkeys.com/book/


#2

On Jun 3, 2006, at 8:52 AM, Pat M. wrote:

 (report-result ,result ',test-form)

I would absolutely love it if I could build syntax like I did with
def report_result(result, &block)
irb> test { (1 + 2) == 3 }

just kind of sucks.
Pat

[1] http://www.gigamonkeys.com/book/

It seems like just giving a Proc a copy of the code used to create it
would do this. There might even be a nice way to do it behind the
scenes that wouldn’t require actually storing a string. But I don’t
see any way to do this myself right now. Maybe some tricks with
stack tracing?
I do agree that Lisp has some neat tricks, but it made my eyes hurt
trying to figure out where all the parens in that code started and
stopped…
-Mat


#3

`(let ((,result ,test-form))
(report-result ,result ',test-form)
,result)))
[…]

Just in case you missed this great article, here it is:

http://www.artima.com/rubycs/articles/patterns_sexp_dsls3.html

and among other things it talks exactly about this: S-Expressions.

Hope it helps,
Alex


#4

On Jun 3, 2006, at 12:17 PM, Elliot T. wrote:

On Jun 3, 2006, at 8:19 AM, Mat S. wrote:

I do agree that Lisp has some neat tricks, but it made my eyes
hurt trying to figure out where all the parens in that code
started and stopped…
most of the parentheses can be ignored because the whitespace
contains the same information. like in ruby we use whitespace to
read nested do/end blocks.

Yeah, that probably came off stronger than I intended. I’m sure like
anything else, you get used to it after awhile. I’m just not used to
it in the least.
-Mat


#5

On Jun 3, 2006, at 8:19 AM, Mat S. wrote:

I do agree that Lisp has some neat tricks, but it made my eyes hurt
trying to figure out where all the parens in that code started and
stopped…

most of the parentheses can be ignored because the whitespace
contains the same information. like in ruby we use whitespace to read
nested do/end blocks.

for the paren that can’t be ignored, the information is necessary to
prevent ambiguity about grouping things together. all languages have
to express that information somehow.

ruby does

foo(bar(x,y),z)

and lisp does

(foo (bar x y) z)

Those are almost the same (all the identifiers are in the same order
(prefix, not infix, for both lisp and ruby)). the lisp version is IMO
a little nicer. certainly nothing worth hating.

ruby does other things as well, like using dots: “c,b,a”.split
(",").sort.join. maybe that is even nicer. lisp could do that too, if
you wanted it to.

– Elliot T.