This morning, I came across this discussion on Reddit. Following the lead of dams and zakame, I had been playing with p5-mop, so I decided to compare.
First, here is singe's original example using Object::Tiny
#!/usr/bin/env perl use v5.18; use warnings; package myBaseClass { use Object::Tiny qw(myID myName); sub doMsg { my($self, $m) = @_; say $m; } } package myDerivedClass { use parent -norequire, 'myBaseClass'; sub hello { say __LINE__.' Hello from '.__PACKAGE__; } } my $v= myDerivedClass->new(myID => time , myName => 'DC'); $v->hello; $v->doMsg($v->myName .' Very nice! How much RAM is Eclipse using?');
How does that compare to not using a framework at all?
#!/usr/bin/env perl use v5.18; use warnings; package myBaseClass { sub new { my($class, %args) = @_; bless {%args}, $class; } sub myID { my $self = shift; $self->{myID} = shift if @_; $self->{myID}; } sub myName { my $self = shift; $self->{myName} = shift if @_; $self->{myName}; } sub doMsg { my($self, $m) = @_; say $m; } } package myDerivedClass { use parent -norequire, 'myBaseClass'; sub hello { say __LINE__.' Hello from '.__PACKAGE__; } } my $v = myDerivedClass->new(myID => time , myName => 'DC'); $v->hello; $v->doMsg($v->myName .' Very nice! How much RAM is Eclipse using?');
In the base class, we have to write our own constructor (new) and our own accessors (myID and myName). The derived class doesn't change at all.
Now, what if we had a MOP built in to Perl? If we have a recent version of Perl installed with a recent version of cpanm, we can try that out lickety split with
$ cpanm --dev twigils $ cpanm git://github.com/stevan/p5-mop-redux.git
This installs the development version of twigils and the github version of p5-mop-redux. These are still being developed, of course, but they are working. What does it look like when we use them?
#!/usr/bin/env perl use v5.18; use warnings; use mop; class myBaseClass { has $!myID is ro = 0; has $!myName is ro = ''; method doMsg ($m) { say $m } } class myDerivedClass extends myBaseClass { method hello { say __LINE__.' Hello from myDerivedClass' } } my $v = myDerivedClass->new(myID => time , myName => 'DC'); $v->hello; $v->doMsg($v->myName .' Very nice! How much RAM is Eclipse using?');
Nice! We have a class keyword for the classes, a method keyword for the methods, and a has keyword for the attributes. And methods have real signatures. I could get used to that very quickly, I think!
What does it look like using Moose? Let's ignore the namespace and immutability issues.
#!/usr/bin/env perl use v5.18; use warnings; package myBaseClass { use Moose; has 'myID' => (is => 'ro'); has 'myName' => (is => 'ro'); sub doMsg { my($self, $m) = @_; say $m; } } package myDerivedClass { use Moose; extends 'myBaseClass'; sub hello { say __LINE__.' Hello from '.__PACKAGE__; } } my $v = myDerivedClass->new(myID => time , myName => 'DC'); $v->hello; $v->doMsg($v->myName .' Very nice! How much RAM is Eclipse using?');
Here we still use package and sub, but we get has and extends.
It looks the same with Moo.
#!/usr/bin/env perl use v5.18; use warnings; package myBaseClass { use Moo; has 'myID' => (is => 'ro'); has 'myName' => (is => 'ro'); sub doMsg { my($self, $m) = @_; say $m; } } package myDerivedClass { use Moo; extends 'myBaseClass'; sub hello { say __LINE__.' Hello from '.__PACKAGE__; } } my $v = myDerivedClass->new(myID => time , myName => 'DC'); $v->hello; $v->doMsg($v->myName .' Very nice! How much RAM is Eclipse using?');
How about MooseX::Declare?
#!/usr/bin/env perl use v5.18; use warnings; use MooseX::Declare; class myBaseClass { has 'myID' => (is => 'ro'); has 'myName' => (is => 'ro'); method doMsg (Str $m) { say $m } } class myDerivedClass extends myBaseClass { method hello { say __LINE__.' Hello from '.__PACKAGE__ } } my $v = myDerivedClass->new(myID => time , myName => 'DC'); $v->hello; $v->doMsg($v->myName .' Very nice! How much RAM is Eclipse using?');
This restores the class and method keywords and the method signatures. Unfortunately, I was disappointed to learn at YAPC::NA this year that MooseX::Declare is frowned upon. Apparently, the magic of Devel::Declare is too deep for modern software engineering. Bummer.
But now there's Moops, which aims to do the same thing without using Devel::Declare.
#!/usr/bin/env perl use v5.18; use warnings; use Moops; class myBaseClass { has 'myID' => (is => 'ro'); has 'myName' => (is => 'ro'); method doMsg (Str $m) { say $m } } class myDerivedClass extends myBaseClass { method hello { say __LINE__.' Hello from '.__PACKAGE__ } } my $v = myDerivedClass->new(myID => time , myName => 'DC'); $v->hello; $v->doMsg($v->myName .' Very nice! How much RAM is Eclipse using?');
Hooray for Moops!
Now this is just one tiny example with only simple inheritance, but I think it's instructive to see several ways to do it in Perl. I've been playing with Go a lot lately, which doesn't even have inheritance; everything is done via composition. In Perl, we would do this with roles. I didn't even mention roles above, but I think I could do another whole post on Role::Tiny, Moose roles, Moo::Role, and mop roles.
Update (2013-09-25): Oops, I forgot Class::Tiny! This example is so small that it looks the same as Object::Tiny.
#!/usr/bin/env perl use v5.18; use warnings; package myBaseClass { use Class::Tiny qw(myID myName); sub doMsg { my($self, $m) = @_; say $m; } } package myDerivedClass { use parent -norequire, 'myBaseClass'; sub hello { say __LINE__.' Hello from '.__PACKAGE__; } } my $v= myDerivedClass->new(myID => time , myName => 'DC'); $v->hello; $v->doMsg($v->myName .' Very nice! How much RAM is Eclipse using?');
By the way, all of these examples do the same thing
$ ./useClassTiny.pl 18 Hello from myDerivedClass DC Very nice! How much RAM is Eclipse using?
except for that changing line number.
one can write vanilla perl oop more compact way:
Posted by: Serge | 09/22/2013 at 02:36 PM
@Andy, those accessors are very different.
There seems to be an under-current of people fighting for vanilla Perl objects. No fight necessary, use what is good for you, voo isn't going anywhere. that said once you love the Moo(se)* revolution, you might not want to go back :-)
Posted by: Joel Berger | 09/23/2013 at 05:04 AM
@Andy - Joel is right, the accessors are very different. You made them rw instead of ro - as did olynshpeegul. Really they should only be this:
sub myID { shift->{myID} }
sub myName { shift->{myName} }
Personally, I am very excited at the prospect of p5-mop, intrigued by moops, allowing of Moo. I love how Moose and MooseX::Declare are pushing the envelope of syntax and design, but haven't been able to justify the full Moose in any of my production settings. I am really excited for p5-mop, I really want to see this culmination of Perl6, Moose, Mouse, Moo, Mo, Moops, MooseX::Declare work all come to fruition in p5-mop.
Thanks olynshpeegul for putting this comparison together.
Posted by: Paul Seamons | 09/23/2013 at 07:37 AM
The original "no framework" accessors in the article are both read-write too, so the problem of them being different to the "framework" ones is oylenshpeegul's rather than just Andy's.
Posted by: Andrew Johnson | 09/23/2013 at 10:30 AM
for me OOP is not fancy keywords, it is ability to model abstraction in certain way.
i just like KISS'ness / YAGNY'ness and flexibility of current Perl approach to OOP.
Everything else should be on CPAN or, at least, compatible with current implementation: class <-> package, methods <-> subs, object <-> blessed ref
2c
Posted by: Serge | 09/23/2013 at 01:32 PM
I'm bummed you didn't also show Class::Tiny. It's similar to Object::Tiny, but you get RW accessors, lazy attribute defaults and a BUILD/DEMOLISH system like you get in Moo(se).
Posted by: Xdg | 09/24/2013 at 05:11 PM
Mea culpa! I added Class::Tiny above. We need a more complicated example to explore the differences between all of these systems. As has been pointed out, I've got read-only accessors in some and read-write in others. Nonetheless, it's still kind of neat to have them all laid like this.
Posted by: oylenshpeegul | 09/25/2013 at 06:11 AM
Thanks for adding it. I think as you get into defaults, lazy defaults, BUILD/DEMOLISH and roles, you'll start seeing clearer similarities and differences.
Posted by: Xdg | 09/26/2013 at 07:10 PM
I wouldn't say that. Yes, I'm familiar with Stevan's ... erm, distaste, shall we say? ... for MXD; I just disagree with it. And to say all Devel::Declare modules are bad is going too far, IMHO.
MXD does have some pretty damning issues, though. Happily they can be addressed by using Method::Signatures::Modifiers. See also my talk here: https://github.com/schwern/method-signatures/blob/master/talks/MSinsideMXD.pdf
Posted by: Barefootcoder.blogspot.com | 10/01/2013 at 10:31 AM
Thanks, barefootcoder, I didn't know about Method::Signatures::Modifiers! If we change the Str to String in the signature in the MooseX::Declare example above, we get
which isn't terribly helpful. But when we add use Method::Signatures::Modifiers, we get
which tells us exactly what is wrong. Nice! Moops fares no better than unassisted MooseX::Declare here
Neither line 48 nor line 43 have much to do with our code.
To be fair to Stevan, when I asked what was wrong with MooseX::Declare, his very sensible answer ended with something like, "but if it's working for you, keep using it."
Posted by: oylenshpeegul | 10/03/2013 at 05:25 AM