

Eric Kidd: Why Ruby is an Acceptable Lisp - raganwald
http://www.randomhacks.net/articles/2005/12/03/why-ruby-is-an-acceptable-lisp#repost

======
emmett
Having hacked a (poorly implemented, bug riddled) macro system into Ruby
because I needed it, I can say with certainty that Ruby is _not_ an acceptable
Lisp. It just turns out that some of the (easiest) things you can use macros
for can be faked with a combination of closures and fully executable class
code (both good ideas, both present in Ruby and in Python). Try to do any of
the most interesting/great things with macros and you'll run into a brick
wall.

Of course this can be overcome - he mentions two projects which could turn
Ruby from most of a Lisp into 100% of a Lisp with direct access to the parse
tree. But those are not used right now in every day Ruby programming.

~~~
raganwald
What are "the most interesting/great things with macros" that you would like
to do?

~~~
emmett
I want to be able to write:

    
    
      cache(key_name, :ttl => 30.seconds, :dependent => ModelClass) do 
         model.expensive_operation
      end
    

and have that cache expire whenever any instance of ModelClass is updated.

With macros, every call to cache() can be expanded into:

    
    
      ModelClass.register_expiration_callback(keyname)
      do_cache(...)
    

Without macros, there's no way to catch deeply nested cache calls. I fake this
by scanning the source for calls to cache() on startup right now.

~~~
avibryant
I'm not following. Why does "ModelClass.register_expiration_callback(keyname)"
have to be in a macro expansion, rather than just the first statement of the
cache() method? It doesn't appear to use local scope at all. Can you flesh out
the example a bit more?

FWIW, I believe that Ruby or (especially) Smalltalk-style syntax for method
calls and closures covers such a large set of the things macros are used for
that their complexity cost (esp when building tools) outweighs their benefit.

~~~
emmett
Sorry, re-reading that it wasn't totally clear. The
ModelClass.register_expiration_callback(keyname) has to be run at _parse time_
, because there's no guarantee that the cache statement will ever be executed
before a ModelClass instance is updated. You can't run things at parse time in
Ruby (without exotic extensions); that's the whole point of macros.

I completely agree that there are a large number of uses for macros that
"SmallTalk style" syntax completely replace. It turns out that a lot of things
don't really require you to run code at parse time. Take anaphoric-if for
example, which is classically implemented as a macro; all you really need is
the ability to manipulate the local variables of a closure from the outside.
Parse-time execution is not required.

------
lispm
'Recursive Functions of Symbolic Expressions and Their Computation by
Machine', McCarthy, 1960

<http://www-formal.stanford.edu/jmc/recursive/recursive.html>

Does that sound like 'Ruby'?

I don't think so.

------
Tichy
What confuses me in Ruby is that functions and executable blocks (or what they
are called) seem to be different things.

So def my_fun(a,b,c) is different from {|a,b,c|...}

I find it very confusing and as a matter of fact, I can not remember what I
have to instantiate when to pass around (there is a Proc object, but does it
refer to a fn or a block? And what would be the name for the other thing?).

Just a reminder that shortness is not the only criterion - it also has to be
intuitive/logical.

~~~
judofyr
This is a method:

    
    
      def nuts(a, b, c)
      end
    

A method can take a block (a block is not an object, it's syntax):

    
    
      nuts { |a, b, c| ... }
    

You can capture a block as a Proc:

    
    
      foo = proc { |a, b, c| ... }
      foo.call(1, 2, 3)
      
      def nuts(&proc)
        proc.call(1, 2, 3)
      end
      
      nuts { |a, b, c| ... }
    

There is also a Method object:

    
    
      method(:nuts).call(1, 2, 3)
    

And you can turn a method into a Proc:

    
    
      method(:nuts).to_proc.call(1, 2, 3)
    

Why does it makes sense to have these as different things? Because we want to
use return in the blocks:

    
    
      def nuts
        @vars.each do |var|
          case var
          when String
            # Returns the method, not the block
            return "Test"
          end
        end
      end

~~~
judofyr
Also, they behave slightly different when it comes to arity:

    
    
      def nuts(a, b)
      end
      
      foo = Proc.new { |a, b| }
      
      nuts(1, 2, 3)     # => ArgumentError: wrong number of arguments (3 for 2)
      foo.call(1, 2, 3) # works fine
    

We also have lambda, which is a Proc that works (almost) as a method:
<http://samdanielson.com/2007/3/19/proc-new-vs-lambda-in-ruby>

