[ANN] DrX, an object inspector

M

Mooffie n/a

Arndt said:
Mooffie said:
(You believe the object graph DrX produces can be generated by existing
documentation tools? I'm not aware of similar tool(s).

doxygen generates a similar [...]

No, it doesn't.

Doxygen, and similar tools, generate diagrams that are *not* suitable
for Ruby:

* Ruby's object model is diagrammed best by using a grid where the
various lines of inheritance are shown orthogonal (in
parallel/perpendicular). That's what DrX does. Doxygen is good for
languages like C++ and PHP, whose class inheritance is mostly a tree.
Ruby's object model isn't a tree, it's a graph (and a circular one!).
And unless this is taken into careful consideration when generating the
DOT source, a very hard-to-read (and thus useless) diagram will come
out. The only reason people _might_ feel good with Doxygen diagrams is
because it blissfully ignores more than half of Ruby's object model (the
singletons hierarchy, and the run-time part).

* In Ruby, much of the object model is generated at run-time. Static
diagramming tools are blind to this. For example, look at the DataMapper
diagram. Static tools like 'rdoc' can't tell us in which classes the
gazillion modules end up 'include'd.




As for GraphViz's SVG problem:

I think I vaguely remember that the font names it puts in the SVG file
aren't standard ones. Perhaps fixing this will alleviate the problem.
I'll need to investigate this. I certainly want SVG support. Let me know
if you have more ideas on this and similar subjects.

Note that I'm not using Tk's canvas to draw the graph. I show the bitmap
GraphViz produces.
 
M

Mooffie n/a

Mooffie said:
Note that I'm not using Tk's canvas to draw the graph. I show the bitmap
GraphViz produces.

I also ask GraphViz to produce a "hot spot" map, which I use to give
visual cues to the user when he hovers the mouse on the various shapes
in the bitmap.
 
A

Arndt Roger Schneider

Mooffie said:
Arndt Roger Schneider wrote:

Mooffie wrote:

(You believe the object graph DrX produces can be generated by existing
documentation tools? I'm not aware of similar tool(s).
doxygen generates a similar [...]

No, it doesn't.

Doxygen, and similar tools, generate diagrams that are *not* suitable
for Ruby:

* Ruby's object model is diagrammed best by using a grid where the
various lines of inheritance are shown orthogonal (in
parallel/perpendicular). That's what DrX does. Doxygen is good for
languages like C++ and PHP, whose class inheritance is mostly a tree.
Ruby's object model isn't a tree, it's a graph (and a circular one!).
And unless this is taken into careful consideration when generating the
DOT source, a very hard-to-read (and thus useless) diagram will come
out. The only reason people _might_ feel good with Doxygen diagrams is
because it blissfully ignores more than half of Ruby's object model (the
singletons hierarchy, and the run-time part).

* In Ruby, much of the object model is generated at run-time. Static
diagramming tools are blind to this. For example, look at the DataMapper
diagram. Static tools like 'rdoc' can't tell us in which classes the
gazillion modules end up 'include'd.




As for GraphViz's SVG problem:

I think I vaguely remember that the font names it puts in the SVG file
aren't standard ones. Perhaps fixing this will alleviate the problem.
I'll need to investigate this. I certainly want SVG support. Let me know
if you have more ideas on this and similar subjects.

Note that I'm not using Tk's canvas to draw the graph. I show the bitmap
GraphViz produces.


Send a medium to large compressed svg via email to me, then I take a
look at it.
I suspect the graphviz/svg issue can be solved inside the DOM-tree after
graphviz generated
the svg.

-roger
 
C

Caleb Clausen

Thanks for reminding me of this Ruby/Tk bug. I think I'll have a
workaround soon. It only happens for Tk 8.4, not for Tk 8.5.

I thought I had 8.5 installed... apparently not. Thanks for the
instructions for fixing it.
Yes, there's a chance. I haven't yet an idea how this feature will
look/behave.

I can give you some ideas, if you want. I realize that since the
contents of an object are potentially a much larger graph, that makes
displaying them rather more challenging.
There are several reasons for not doing this. Jakub has explained. I
certainly don't want to use my EDITOR: mine works only in the console,
and gedit starts up faster.

EDITOR is supposed to be a console-only program. You'd have to launch
a new xterm (or whatever) and run EDITOR in that. If the user wants a
gui program, he should place it in VISUAL. Jakub's scheme of looking
in DRX_EDITOR_COMMAND, then VISUAL, then perhaps EDITOR is probably
the best.

But really this was a very minor quibble, so feel free to ignore me.
(Besides, I don't recommend people to use this environment variable
anyway. They should put all their configuration in ~/.drxrc: it's much
better because they can backup it, copy to other systems, etc. This
DRX_EDITOR_COMMAND exists only because DrX didn't have ~/.drxrc when I
wrote that section in the user manual.)

And that's quite appropriate, of course. But it brings up another
point; since this is a gui program, shouldn't it have gui dialog(s)
for setting all those configuration items? That's always nice to have,
but again of minor importance at best.
 
D

David Masover

For getting the data, you don't even need to write an extension to
JRuby. The "jruby" library gives you introspective access to all
objects at a very direct level:

require 'jruby'

# no JRuby on this machine, so I'm doing this from memory
some_str = 'hello!'
ref = JRuby.reference(some_str)
ref.class # now org.jruby.RubyString
ref.methods # now includes all Java-land methods on RubyString
cls = ref.metaclass # an org.jruby.RubyClass instance
cls.methods # should be the internal method table

It seems like this should be possible to do from bare Ruby, in a portable way.
Clumsy, but possible. For example, as long as people don't get smart and use
Ruby 1.9's BasicObject, you can do this:

Object.instance_method:)methods).bind('foo').call

That takes care of things like Builder's BlankSlate object, though:

Object.instance_method:)methods).bind(BlankSlate.new).call
=> ["__send__", "instance_eval", "__id__"]

BlankSlate itself hints at other ways to abuse Ruby for similar effects -- for
instance, I couldn't readily find a way to discover where a method was
originally defined, and whether it's been overridden, but it seems likely that
if I could get my fangs deep into things like method_added before anything
else was loaded, I could generate this kind of map.

I might be missing what you're trying to do, though.
 
C

Charles Oliver Nutter

It seems like this should be possible to do from bare Ruby, in a portable way.
Clumsy, but possible. For example, as long as people don't get smart and use
Ruby 1.9's BasicObject, you can do this:

No, I'm pretty sure this isn't possible on regular Ruby. What's
happening in JRuby is that you're getting direct access to the Java
object representing a Ruby class or object, and you then can do
anything to that object you'd normally have to write Java code for.

Now that I'm on my machine, here's a more extensive example:

require 'jruby'

class MyObject
def foo
'hello'
end
end
obj = MyObject.new
cls = obj.class

obj_ref = JRuby.reference obj
cls_ref = JRuby.reference cls

# accessing the raw method table on a class
methods = cls_ref.getMethods # this was the ".methods" call previously
p methods.class # => Java::JavaUtilConcurrent::ConcurrentHashMap
foo_method = methods['foo']
# foo_method is an instance of our internal method object
p foo_method.call(JRuby.runtime.current_context, obj, cls_ref, 'foo')
# => 'hello'

# changing the object's class(!?)
class Foo; end
p obj.class # => 'Object'
obj_ref.setMetaClass(JRuby.reference(Foo))
p obj.class # => 'Foo'

# modifying the current scope
scope = JRuby.runtime.current_context.current_scope
p scope.static_scope.variables.to_a
# => ["obj", "cls", "obj_ref", "cls_ref", "methods", "foo_method", "scope"]
index = scope.static_scope.variables.to_a.index('scope')
scope.set_value_depth_zero('hello', index)
p scope # => 'hello'

...and so on. Essentially anything you can call from Java, you can
call from Ruby once you have the appropriate reference, so you should
be able to programmatically walk all of the JRuby runtime structures
and any Ruby or Java objects they reference without writing any Java
code.

- Charlie
 
M

Mooffie n/a

David said:
It seems like this should be possible to do from bare Ruby, in a
portable way.
Clumsy, but possible.

How, for example, can you find an object's class using pure Ruby?

The problem with Ruby's #class is that it skips singletons. Example:

s = "blah"
def s.whatever; 666; end
p s.class

The above will print "String", but it's an error: the class should be
some singleton.

(Doing "class << s; self; end" isn't a solution because it alters the
object model: it *creates* a singleton if none yet exists.)

Though maybe there *is* a way to find an object's class, I don't know (I
still consider myself a Ruby newbie).

It seems like this should be possible to do from bare Ruby, in a
portable way.

As another example, consider this:

module M
def whatever; 666; end
undef_method :whatever
end

M.instance_methods() won't report the method 'whatever', whereas Ruby
keeps a stub under that name. I want to see it.

When I started writing DrX my intention was to figure out how Ruby
and/or MRI works. So high-fidelity was important to me. In fact, I
simply wanted to see the C structures. I'm not interested in
approximation.

(Though nothing prevents replacing the C extension with a pure-Ruby
implementation.)
 
C

Caleb Clausen

How, for example, can you find an object's class using pure Ruby?

The problem with Ruby's #class is that it skips singletons. Example:

s = "blah"
def s.whatever; 666; end
p s.class

The above will print "String", but it's an error: the class should be
some singleton.

(Doing "class << s; self; end" isn't a solution because it alters the
object model: it *creates* a singleton if none yet exists.)

Though maybe there *is* a way to find an object's class, I don't know (I
still consider myself a Ruby newbie).

I had thought that I had proved to myself that class<<s; self; end was
guaranteed to be nondestructive on all modern (>=1.8.6) versions of
ruby.... maybe not, tho. I'm not sure now. I might need to change some
of my own code in light of this....

Your terminology is a bit wrong, or at any rate unorthodox.
Object#class works as it was designed. In the above snippet, the class
of s is always a string, even tho its singleton class changes. The
#class of an object is its birth type, even tho the type can change
subsequently. (The klass field of an object doesn't always point
directly to an object's class... klass is kind of misnamed, actually.)

Recent vintages of 1.9 are supposed to have Object#singleton_class.
 
D

David Masover

No, I'm pretty sure this isn't possible on regular Ruby.

Ah, wishful thinking of my part, then.
What's
happening in JRuby is that you're getting direct access to the Java
object representing a Ruby class or object, and you then can do
anything to that object you'd normally have to write Java code for.

I've raised this question before, and people seem to disagree with me, but:
# changing the object's class(!?)
class Foo; end
p obj.class # => 'Object'
obj_ref.setMetaClass(JRuby.reference(Foo))
p obj.class # => 'Foo'

I think it should be possible to do that from within Ruby.

But I can't even get most people behind this suggestion:

class A; end
class B; end
A.instance_method:)to_s).bind(B.new)

That gives me a type error, which just seems bizarre, especially after the
relative freedom of JavaScript. Ah, well.
 
C

Charles Oliver Nutter

I think it should be possible to do that from within Ruby.

But I can't even get most people behind this suggestion:

class A; end
class B; end
A.instance_method:)to_s).bind(B.new)

That gives me a type error, which just seems bizarre, especially after the
relative freedom of JavaScript. Ah, well.

The main issue that comes up with this is that classes with native
methods (like all the core classes) would have to be given special
treatment. You couldn't, for example, rebind a String method to Array
or an Array subclass since the in-memory structure is different. In
short, you'd at best end up with methods that don't function because
they're not attached to an array, and at worst with methods that
outright segfault.

There's no technical reason you couldn't allow rebinding of Ruby
methods though. That's roughly possible in JRuby if you go "under the
covers":

require 'jruby'

class Foo
def foo
puts "This is foo in #{self.class}"
end
end

class Bar
end

foo_ref = JRuby.reference(Foo)
bar_ref = JRuby.reference(Bar)

bar_ref.add_method('bar', foo_ref.search_method('foo'))

Bar.new.bar # => "This is foo in Bar"

...of course, the devil's in the details.

- Charlie
 
D

David Masover

The main issue that comes up with this is that classes with native
methods (like all the core classes) would have to be given special
treatment. You couldn't, for example, rebind a String method to Array
or an Array subclass since the in-memory structure is different. In
short, you'd at best end up with methods that don't function because
they're not attached to an array, and at worst with methods that
outright segfault.

I think I'm OK with that. JavaScript doesn't allow rebinding native methods,
either. I would, however, strongly encourage fewer native methods when
possible -- doesn't Kernel#autoload _still_ use a native call to the native
require, thus bypassing any attempt to overload Kernel#require?
There's no technical reason you couldn't allow rebinding of Ruby
methods though.

Just a political one, I think. Every time I bring this up, I get a weak
justification that seems to be based around some desire for strong, static
typing, and strict, rigid inheritance.

It's especially frustrating when, as in my example above, I'm trying to rebind
a method which was originally declared on a parent class to both methods.
Having to figure out (or programmatically navigate!) the class hierarchy to
find the exact point which is a superclass (or supermodule) of both objects
I'm working with, yet still has the appropriate method, and then rebind it all
the way down the hierarchy, rather than at least having it Just Work
(A.instance_method:)to_s) should return Object#to_s unless I actually define
A#to_s), is the exact POLAR OPPOSITE of duck typing, do-what-I-mean, beautiful
code, and everything Ruby is supposed to be about. Let me decide if the class
is important -- if I really care, I can check obj.kind_of?(umeth.owner), but
there's no good reason for the language to make that decision for me! Ruby is
not supposed to be like Java!

*deep breaths*

I can definitely think of a few places where this would be useful. I can
probably hack around it anyway, though -- it mostly just offends the purist in
me. It is, IMO, the ugliest thing about Ruby's otherwise-beautiful object
model.
That's roughly possible in JRuby if you go "under the
covers" [snip]
...of course, the devil's in the details.

As much as I like JRuby, I'd much prefer a portable solution. However, I
needed this for a project I've all but abandoned, so I'm not particularly
motivated to write such a solution. (I'd guess the closest I would get is a
single gem that does different hacks in different interpreters.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,156
Messages
2,570,878
Members
47,408
Latest member
AlenaRay88

Latest Threads

Top