So, that was the disclaimer; now onto the fun stuff!
Breaking encapsulation (even more)
As you know, in Ruby everything is accessible in some form or another, and you can do almost everything with the metaprogramming facilities. Well, except for one small detail which I found out while working on the AR-JDBC database drivers.
We have some code there which needs to be separate for each database, and it just so happens that core ActiveRecord have already implemented them in a very good way. So, what do we do? Mix in them and remove the methods we don't want? No, because ActiveRecord adapters are classes, not modules, and you can't mix in classes. There is no way to get hold of a method and add that to an unrelated other class or module. Except if you're on JRuby, of course:
require 'jruby/ext'Of course, using this should be avoided at all costs. But it's interesting that such a powerful thing can be implemented using about 15 lines of Java code.
class A
def foo
puts "A#foo"
end
def bar
puts "A#bar"
end
end
class B;end
class C;end
b = B.new
b.steal_method A, :foo
b.foo
B.new.foo rescue nil #will raise NoMethodError
C.steal_methods A, :foo, :bar
C.new.foo
C.new.bar
Introspection
JRuby parses Ruby code into an Abstract Syntax Tree. For a while now, the JRuby module have allowed you to parse a string and get the AST representation by executing:
require 'jruby'This returns the Java AST representation directly, using the Java Integration features. That is old. What is new is that I have added pretty inspecting, a nice YAML format and some navigation features which makes it very easy to see exactly how the AST looks. Just do an inspect or to_yaml on an AST node and you will get the relevant information.
JRuby.parse "puts 'hello'", 'filename.rb', false
That is interesting. But what is even more nice is the ability to run and use arbitrary pieces of the AST (as long as they make sense together) and also run them:
require 'jruby'As you can see, I take two fragments from different code, add them together and run them. You can also see that I'm using an alias for parse here, called ast_for. That makes much more sense when using the second parse feature, which we already know from ParseTree:
ast_one = JRuby::ast_for("n = 1; n*(n+3)*(n+2)")
ast_two = JRuby::ast_for("n = 42; n*(n+1)*(n+2)")
p (ast_one.first.first + ast_two.first[1]).run
p (ast_two.first.first + ast_one.first[1]).run
require 'jruby'Well, I guess that's all I wanted to show right now. These last small things I've added because I believe they will be highly useful for debugging JRuby code.
JRuby::ast_for do
puts "Hello"
end
I also have some more ideas that I want to implement. I'll keep you posted about it.
6 kommentarer:
require 'evil'
class Bar
include Array.as_module
end
b = Bar.new
b.push(1,2,3)
p b # => [1,2,3]
Yup, I think I would have allowed including classes as well. evil.rb also offers Module#inherit so you can do:
class Bar
inherit Array
end
I think that is a bit nicer than steal_methods.
Still, very cool stuff. Being able to get at the AST is especially cool. Bonus points if you can add it to Method objects and Proc objects. :)
hey ola
can you try to add code indention to your code?
right now i.e. ruby code lacks indention, and it makes it a bit hard to read the ruby code without indention :)
whops, sorry, i forgot to add:
i meant code indention in your BLOG _here_ :)
Daniel, do you know if evil.rb works outside of MRI?
Ola, nice going - very cool. What approaches have you tried in simulating this behavior in Ruby?
Is it a function of simply knowing the AST and what would and would not work, or have you tried doing this?
I tried by instantiating A, then getting foo as a method, converting it to a proc, and using defined method with that info on B.
That works for your simple example, but as you probably know, if you have foo accessing some member variable, you will not get what you want - it uses A's members, at the time you stole the method.
@sammy,
No, because evil.rb uses DL, which is a C extension. However, there's nothing preventing a DL wrapper in JRuby via JNI, at least in theory.
And no, I'm not going to write one. I'll let YOU write one. :)
Skicka en kommentar