20 Oct 2010

Macros make the world go 'round (part 1)

This post is a response to Mike Stone's post about closures in Ruby. (Just ignore the rant about the conditional operator; nothing to see there. :-P)

Closures are, of course, useful. I think Lisp languages take closures even further, making them even more useful. How? With macros, of course.

Here's a simple if contrived example: Most math libraries have a log10 function, same as log but using base 10 instead of base e. Now, another useful operation is a base-2 logarithm. Let's call it log2.

Here's a naive way to implement it in Scheme:

(define log2
  (lambda (n)
    (/ (log n) (log 2))))

This is all fine and good, but notice that log(2) is recalculated every time, which is redundant. So, let's hoist the log(2) out of the actual lambda:

(define log2
  (let ((ln2 (log 2)))
    (lambda (n)
      (/ (log n) ln2))))

Heck, you can go even further, and turn the function into a multiplication instead of a division:

(define log2
  (let ((invl2 (/ (log 2))))
    (lambda (n)
      (* invl2 (log n)))))

I could play with the code even further (something I'll explore in my next post), but this will do for now. :-)

The idea with all the above is that you calculate log(2) just the once, and have its value available for only the log2 function, without polluting anybody else's environment.

In order to implement it in Ruby, this seems to be the easiest way I can think of (feel free to suggest something simpler):

class << Math
  define_method :log2, lambda {|invl2|
    lambda {|n| invl2 * Math.log(n)}
  }.call(1 / Math.log(2))
end

This is quite an unintelligible way to define the function: first, we're having two layers of lambda, then calling one of them.

And now, we come to the punchline. :-) In Scheme, the code would actually do pretty much the same thing as the Ruby version behind the scenes, but the outer lambda and the call to it are nicely wrapped up in the let macro. Compare the last Scheme version:

(define log2
  (let ((invl2 (/ (log 2))))
    (lambda (n)
      (* invl2 (log n)))))

with its macro-expanded version:

(define log2
  ((lambda (invl2)
     (lambda (n)
       (* invl2 (log n))))
   (/ (log 2))))

If someone wrote a let macro for Ruby, I think the Ruby version of the code could be similarly pretty.

1 comment:

Anonymous said...

Thanks for an idea, you sparked at thought from a angle I hadn’t given thoguht to yet. Now lets see if I can do something with it.