Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw

Re^3: Reopening builtin classes, redefining builtin functions?

by stvn (Monsignor)
on Dec 09, 2007 at 22:24 UTC ( #656019=note: print w/replies, xml ) Need Help??

in reply to Re^2: Reopening builtin classes, redefining builtin functions?
in thread Reopening builtin classes, redefining builtin functions?

Moose is based on Class::MOP which is a Meta Object Protocol written in Pure Perl. It is also a meta-circular MOP, which means that at some point it bootstraps and redefines part of itself using itself. The end result is that Class::MOP::Class is itself an instance of Class::MOP::Class (to be more technically correct, Class::MOP::Class->meta is an instance of Class::MOP::Class because a class name in Perl is not an object, but just a string and so we need to store the metaclass instance somewhere). Again, this is all defined in Pure Perl, so it is 100% accessible to your regular Perl code. Ruby defines Class in object.c (as well as Module, Object and a few other core bits), and the guts of these things are only as accessible as the language wants them to be (which is quite a lot, but not nearly as much as Class::MOP though). This difference is critical to the power of the MOP.

In Ruby, in order to get the metaclass, you have to do the idiomatic class <<self; self; end, and then you can hack on the metaclass. This is tricky and can get really ugly really quickly (DISCLAIMER: I am not an expert Ruby hacker, so there may be some secrets that I am missing out on, but I found little online or in books to contradict this statement). Most of the stuff I have seen which does anything interesting with this, ends up using the *_eval methods found in Module. Now, I have no problem with string-eval-ing code when you are deep in the meta-world, I do it a lot in Moose to optimize accessors and such. However in Moose it is a choice, in Ruby it is not. This means that there is a fundamental limit to how reusable your metaclass trickery can be and more importantly how maintainable it is (string evaled code is really annoying to maintain, I know).

One of the key things you can do in Moose that (AFAIK) you cannot do in Ruby is to extend the meta layer. Ruby gives you access to the metaclass and through modules and mixins you can somewhat alter a classes behavior in a reusable fashion. But thats where it ends, with Moose you can literally extend Moose with Moose. Here is a classic metaclass example of a class which counts it's instances.

package CountingMetaClass; use Moose; extends 'Moose::Meta::Class'; has 'count' => ( is => 'rw', isa => 'Int', default => sub { 0 } ); after 'construct_instance' => sub { my $class = shift; $class->count($class->count + 1); };
Then in your code you can do:
package Foo; use metaclass ('CountingMetaClass'); # tell it what meta you want used + first ... use Moose;
An equivalent Ruby version would require some serious meta-programming to both wrap the new method, as well as create and install a custom metaclass (honestly I have no clue as to how it would be done, and even if it could be done).

(Yes, I know thats a contrived example, but this is a simple example to illustrate my point, there is lots of useful and amazing metaclass hackery going on in the #moose channel on, stop by if you want to see some).

Now, aside from metaclasses, Moose (though Class::MOP) also provides some sub-protocols. The most powerful one (IMO anyway) is the Attribute protocol, you can find a nice cookbook entry for it here which goes into a lot of detail about how you can easily extend this protocol to get some nice features. You can also use the Instance protocol to customize the instance type which Moose uses, which is by default a HASH ref. The module MooseX::GlobRef::Object provides an excellent example of how to extend this protocol to make your classes use a GLOB ref. Of the cool parts about this is that it works on a per-class basis, and is not a system wide change, so it won't break your other stuff.

Now throw into this Roles, which are a more sane version of mixins, the fact that you can have true Multiple Inheritance if you want it, and most importantly (at least for me) access to the CPAN (yes, I know, rubygems, etc,.. but it is no CPAN).


Replies are listed 'Best First'.
Re^4: Reopening builtin classes, redefining builtin functions?
by djberg96 (Acolyte) on Dec 10, 2007 at 02:49 UTC
    I have no idea what you're going on about with "meta-circular MOP", but in Ruby you can extend a class with a module. Your example requires no metaprogramming in Ruby:
    module Count def new(*args, &block) @count ||= 0 @count += 1 super(*args, &block) end def count @count ||= 0 end end class Foo extend Count end
    Regarding the attribute protocols, it looks like Ruby's attr_accessor with some extra checks tossed in. There are third party libs out there to do this for Ruby, but it's trivial to roll your own.

    Lastly, I don't know what CPAN has to do with this discussion.

      Lemme start by saying, I am not looking for a Ruby v. Moose flamewar. I like Ruby a lot, in fact it inspired some parts of Moose (the other parts were inspired by 2 of Ruby's inspirations, CLOS and Smalltalk). You asked for details, and I am am giving you them.

      I have no idea what you're going on about with "meta-circular MOP"

      The MOP (or Meta Object Protocol) is basically the "API to the Object System". The Ruby equivalent of a MOP (there is no formal complete one, but a collection of bits which can be roughly called a MOP when smushed together), is a combination of the ObjectSpace, Kernel modules, the Class, Object and Module classes. A MOP provides a means with which to introspect/reflect and in some cases alter the internals of the languages object system. There are a few different kinds of ways to approach building a MOP, the two I am most familiar with are the meta-circular style (see CLOS) and the non-meta-circular ones (Pythons metaclasses could be described this way (last time I looked) and some C++ metaclass systems as well).

      A non-meta-circular MOP is one where the Class is an instance of a MetaClass, and the MetaClass is an instance of a MetaMetaClass, etc etc etc. This can go on forever and is sometimes called "turtles all the way down". You basically decide at which point your system "ends" and then that layer just magicaly springs into existence and there is nothing beyond it. Ruby's object system is kind of like this, the Class and Object spring into existence in object.c and a metaclass is not exactly an instance of Class, but instead a kind of odd specialty "singleton class". Ruby borrows heavily from Smalltalk here in that metaclasses are implicit and there is a parallel metaclass hierarchy that matches the class hierarchy. Because metaclasses in Ruby are implicit, you have little control over the creation of them, the best you can do it alter them once they exist by adding singleton methods to them (which is what you basically do when you mess with class <<self; self; end).

      A meta-circular MOP is one where there you have "tied the knot" or "bootstrapped" the system together. This is done by making the class Class itself an instance of Class (note the capitalization here). The result is you end up with a nice little loop at the top of your object system and you suddenly have a system which is implemented in itself. Meta-circularity is a common thing in the LISP and Scheme world where there is such a blurring of code and data that it is easy to write things like a LISP interpreter in LISP. Meta-circularity allows you to easily extend your systems with the system itself, see my previous example in which I basically extended the behavior of Class. (Yes, in Ruby you have open classes, so it is possible to extend Class, but its a global change, which brings with it a whole slew of problems). Like I said before, it's hard to give a really practical (yet simple) example of how metaclass programming can be helpful because they contain the power to alter the behavior of the very core of your object system, and thats just not simple stuff.

      Your example requires no metaprogramming in Ruby

      I almost supplied pretty much that same code for a Count module, but I decided against it. (You can do the exact same thing in Perl using a package level variable as a counter, although the lack of consistent OO makes wrapping &new a little trickier, but still it does work). Like I said above, the example is basically too simple and metaclasses are not usually simple things.

      Regarding the attribute protocols, it looks like Ruby's attr_accessor with some extra checks tossed in.

      It is similar to Ruby's attr_accessor but not exactly. Ruby just stashes information into the class about each attribute (usually just a string name) and then the methods are just added to the class normally. Moose does it differently, it treats attributes a first-class elements in the object system so each attribute has it's own meta-object to back it up. These attribute meta objects are fully introspectable, and can be customized on a per-attribute basis (you can have several different custom meta-attirbutes in a single class). You can get all sorts of practical stuff with custom attributes, take a look at MooseX::Getopt and MooseX::AttributeHelpers for just a few examples.

      There are third party libs out there to do this for Ruby, but it's trivial to roll your own.

      I don't know many of these 3rd party libs you speak of, so perhaps there is some cool stuff I am missing, Links would be helpful. As for rolling your own, just a trivial accessor generation system is trivial (see all the crap that is on CPAN), but Moose attributes are so not trivial. Just flip through some of the examples in the cookbooks to see what it can do, like lazy initialization, easy to use (but really powerful) delegation, type checking and coercion, all sorts of stuff. And Moose and Class::MOP are really well tested with over 150 test files and 4000 unit tests between them, so why re-invent that wheel?

      Lastly, I don't know what CPAN has to do with this discussion.

      You asked what Moose can do that Ruby cant. Moose can easily use the accumulated knowledge and battle tested code of CPAN. In fact Moose was built specifically to play well with existing Perl 5 code bases and not require too huge a change.

      Anyway, so thats it. The differences are subtle on the surface, but fairly radical if you go deeper down. Of course if you don't care about any of that shit, then feel free to ignore me.


Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://656019]
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (3)
As of 2018-02-25 00:38 GMT
Find Nodes?
    Voting Booth?
    When it is dark outside I am happiest to see ...

    Results (312 votes). Check out past polls.