RLisp identifiers don't have to be Ruby identifiers -
field-get
and ..
are perfectly valid in RLisp but not in Ruby. Normally that's not a problem, Ruby Symbol
s can contain arbitrary strings, names of RLisp variables and functions are used only inside the compiler, and define_method(:method, ...)
and send(:method, ...)
couldn't care less about what :method
looks like stringified.There's just one tiny problem - using them breaks
Delegator
and other classes which use text-based runtime code generation.class Delegator
def initialize(obj)
preserved = ::Kernel.public_instance_methods(false)
preserved -= ["to_s","to_a","inspect","==","=~","==="]
for t in self.class.ancestors
preserved |= t.public_instance_methods(false)
preserved |= t.private_instance_methods(false)
preserved |= t.protected_instance_methods(false)
break if t == Delegator
end
preserved << "singleton_method_added"
for method in obj.methods
next if preserved.include? method
begin
eval <<-EOS
def self.#{method}(*args, &block)
begin
__getobj__.__send__(:#{method}, *args, &block)
rescue Exception
$@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
$@.delete_if{|s| /^\\(eval\\):/ =~ s}
::Kernel::raise
end
end
EOS
rescue SyntaxError
raise NameError, "invalid identifier %s" % method, caller(4)
end
end
end
end
Delegator
iterates over all methods of an object (obj.methods
), and for each of them generates and compiles a simple wrapper:def self.#{method}(*args, &block)
begin
__getobj__.__send__(:#{method}, *args, &block)
rescue Exception
$@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
$@.delete_if{|s| /^\\(eval\\):/ =~ s}
::Kernel::raise
end
end
If Ruby had real macros like RLisp it wouldn't be a problem. Unfortunately Ruby code is generated by simple string substitution, and if
method
is anything else than a Ruby identifier it breaks.In Ruby
def
is not accessible in any way other than through eval
. We cannot use define_method(method, ...)
as in Ruby 1.8 it cannot define methods which take blocks. And it has different argument-handling semantics than def
, so it wouldn't work anyway.Every use of text-based runtime code generation is a failure of language's reflection model.It's weird how Ruby can be so well-knows for its metaprogrammability, and at the same time have most of it inaccessible through any means other than text-based
eval
. I think improving metaprogrammability is a much more important for future viability of Ruby than improving performance or other popular whining points.So what can RLisp do:
- Accept that
Delegator
and everything that uses it liketmpfile
andwebrick
is broken. - Mangle all non-Ruby symbols into valid Ruby symbols. So
(method foo-bar ...)
would really definefoo_bar
etc. - Monkey-patch
Delegator
to ignore such methods instead of raising an exception. Ugly. - Monkey-patch
Delegator
to delegate such methods with slightly incorrectdefine_method
instead of raising an exception. - Improve Ruby metaprogrammability.
Yes, yes, yes, 1000 times yes.
ReplyDeleteRuby is so nearly there for some stuff, and in other places it's pure pain. I don't mind the lack of macros so much as I mind the lack of metaquoting.
I agree, it's so absolutely horrible you have to do that, and it has bit me oh so many times. At least it's fixed in 1.9.
ReplyDeleteWe reap what we sow.