Perl and Me, Part 3: A Møøse once bit my sister

This is part 3 of an ongoing series where I explore my relationship with Perl.  You may wish to begin at the beginning.

This week we look at the joys—and the frustrations—of Moose.

Last week I talked about why I believe that at least having the choice to program object-orientedly (without it being a huge pain in the ass) is vital to me as a programmer.  In the comments I also touched briefly on why I think OOP in Perl, pre-Moose, is a huge pain in the ass, but honestly I didn’t put much effort into making that point.  In my experience with other programmers (both in real life and online), the majority of them—perhaps 75%, at a rough guess—already agree with that and don’t need any convincing from me.  And the rest are most likely not going to be convinced no matter what I say.  Besides, this isn’t really a persuasive essay.  I’m not trying to change anyone’s mind.  It’s the story of my relationship with Perl.  And I, like most other coders I know, have placed some value in OOP, at least in some circumstances.  Also, I, like most other coders I know, felt that doing OOP in Perl was a bit of a chore.

Then we got Moose.

When I first heard of Moose, I was intrigued.  After all, my primary exposure to OOP was via C++, and I’m sure many of you will have been thinking to yourselves this whole time: “but C++ isn’t even a very good implementation of OOP.” In fact, OOP purists regularly disparage C++.  But I always liked it, in part because of its simplicity.  It has its warts, obviously, but for the most part it Just Works.  Moose, on the other hand, was a step up.  Maybe not as hardcore OOP as, say, Smalltalk, but still offering more power than what I was used to from C++.  Method modifiers and roles, for instance, were things we didn’t have in C++, and I thought they were pretty nifty.  I was actually an early adopter of Moose (as you might imagine from my history thus far), but I abandoned it eventually.  The problem?  Speed.

Moose had an early reputation for being slow.  Is the reputation deserved?  Well, back then I believe it was.  It was slow to run, and very slow to start up.  I liked writing in it—to a point—but I disliked waiting for it.

The story today is vastly different.  Anyone who tells you today that Moose is still too slow is trying to use Perl like it was C.  Believe it or not, there was a time (that I can recall, even) when people accused C of being slow.  Not like Assembly, they would say: now, Assembly ... that’s fast!  Well, sure, you’re always going to give up some speed for greater expressiveness.  But I don’t program in C like I was programming in Assembly—and I haven’t programmed in Assembly since I was a teenager, because it’s just painful.1  Likewise, I don’t program in Perl like I was programming in C ... and, likewise, I don’t program in C any more.  Just as painful, in its own way.  I went to Perl to move beyond that.  The more experienced I get, the more abstractly I want to think about a problem.  And my language better keep up.

Moving from C++ to Perl was mostly a step up, except for that pesky problem of objects being a pain in the ass.  Now here was Moose, aiming to solve even that dilemma.  So, was I willing to give up a little speed for that?  Hell yeah.  Early Moose, though, was forcing me to give up a lot of speed.  Modern Moose?  Not so much.

The Perl I was writing before isn’t any slower than the Perl I write today using Moose.  There was just a lot more of it, because I was trying to do most of what Moose now does for me on my own.  Anyone who has been in this business for any length of time will tell you that measuring programmers by how many lines of code they create per day is moronic.  You can measure the really good ones by how many lines per day they remove.  And Moose helped me remove a metric shit-ton of code.2

So, shorter code, just as fast (or not enough slower that I can actually notice it, anyway), built-in OOP that’s more extensive than C++—what’s not to love?  Well, it’s a lesser complaint (although I won’t go so far as to call it a minor one), but it’s always stuck in my craw: the syntax bugs me.

Syntax doesn’t bother a lot of people, or at least they say it doesn’t.  And I believe many of them.  But I’m pickier about syntax.  The problem I have with Moose (sans extensions) is what I call “cognitive rewriting.”3 Let me try to explain what I mean here.

Here’s the first line of a C++ derived class:

    class Foo : public Bar
When I read this line, I say to myself, “okay, what follows is going to be a class called Foo, which isa Bar; got it.” Seems simple enough.


Now, here’s the first line of the Perl-with-Moose equivalent:

    package Foo;
When I read this line, I say to myself, “okay, what follows is going to be a package—which is nothing more than a namespace, really.  Got it.” Then, sometime later (hopefully not too much later):
    use Moose;
“Okay, no, it’s not a package (except in the sense that all classes in Perl are really just packages); it’s a Moose class, called Foo.  Let me rearrange my expectations a bit.” Then, a bit later:
    extends 'Bar';
“Okay, so it’s still a Moose class, but not a base class: it’s class called Foo which isa Bar.  Yep, okay, rearranging ... got it.” This is something you learn to do if you program in Moose.  Hopefully those three lines are fairly close together, and hopefully there isn’t much of anything else distracting between them ... although it gets worse with roles, because you quite often have to put your with after your attributes, otherwise it doesn’t work properly.  That’s some serious lag before you start to get a complete picture of what you’re reading.  But often it’s not that bad, and you just get used to it.


You got to used to having function parameters coming in via a bizarro var called @_ too.  But that doesn’t make it any less weird.

That isn’t my only problem with Moose, of course.  How about these two lines, found at the bottom of nearly every Moose class?4

    no Moose;
    __PACKAGE__->meta->make_immutable;
Those are what I generally refer to as “the magical Moose incantations.” And you better make sure you put those in your code, because if you don’t ... your code will still work.  For now.  Maybe forever.  Or maybe not.  Oh, and also it will be very slow, sometimes.  Which you may not notice right away.  Or you might.


Aren’t computers supposed to get rid of repetitive boilerplate for me?

So, is it any wonder that I prefer this?

    class Foo extends Bar
That’s arguably even better than the C++ style.  You can get this most easily from MooseX::Declare, but you can also get it from the p5-mop, or from Perl 6 (or, as Joel reminded me in the comments, Moops).  But I’ve chosen MooseX::Declare as being the most production-ready method of getting the syntax I want.


Oh, people try to talk me out of it all the time.  They tell me it’s slow, and buggy, but it isn’t, as long as you replace MooseX::Method::Signatures with Method::Signatures.  They tell me that MooseX::Declare uses Devel::Declare, which mst says is a big bag of crack, and he wrote the damn thing.  To which I respond: a) every piece of code mst ever wrote (that I’ve seen) is a big bag of crack, but that doesn’t change the fact that it’s all dead brilliant,5 and b) just because mst doesn’t like something doesn’t mean I can’t use it, authorship notwithstanding.  They tell me that it isn’t Perly, that it’s trying to turn Perl into something like C++.  To which I reply ...

Yes.  Exactly.

That’s what I want.

Well, obviously I don’t want Perl to turn completely into C++, otherwise I could’ve just stuck with C++ in the first place.  But I switched to Perl because of it’s ability to Get Shit Done, which is like crack for programmers.  But there was this one area where C++ was allowing me to Get Shit Done and Perl was failing me: objects.  Now I can have that in Perl, but even better, and with more functionality.  So am I willing to suffer a little bit to get it?6  Damn skippy.

Because the legibility of my code is very important to me.  Next week we’ll see if we can’t figure out exactly why.


1 In fact, the Assembly I learned didn’t even have a multiply opcode.  If you wanted to multiply things, you wrote a loop.


2 A metric shit-ton is, of course, a tiny bit larger than an imperial shit-ton, which makes it even better for silly exaggerations.


3 I say, “I call it” that because I never heard anyone else use that term.  Although Google tells me that other people have used the term, so perhaps it was stored it in my subconscious somewhere.  In any event, I suppose I’d best not try to take credit for inventing it.


4 Or sometimes you’ll have use namespace::autoclean up at the top instead of that first line at the bottom.  But that doesn’t detract from my point any.


5 In fact, many of my favorite CPAN authors, such as Ingy and the Damian, have this tendency.  I think it’s one of the characteristics of genius, personally.


6 And, honestly, I’m not suffering that much.  In my experience, the problems of Devel::Declare are overrated.  But I suspect we’ll touch on that later in this series.


14 Comments

I nominate this as the best blog post title evar!

Also, at least for personal use, I have moved from mxd+ms to Moops. You might find it a try (though I know you have strong ties to ms)

when is pretty much the only Method::Signatures feature that Kavorka doesn't implement, basically because I can't think of many situations when this:

method foo ($x = 42 when { CONDITION })
{
   ...;
}

Isn't just as clearly written as:

method foo ($x?)
{
   $x = 42 if CONDITION;
   ...;
}

In the spirit of scientific enquiry, I have looked for instances of when being used in the wild (e.g. in CPAN code), that might be able to convince me of its utility. However, I have not been able to find any cases where when is used at all.

Anyone who tells you today that Moose is still too slow is trying to use Perl like it was C.
This is laughably false. What about people who are trying to use Perl like a Unix scripting language? Moose takes 0.25-0.5 seconds just to load, and is at least 1000x slower than plain Perl at creating classes. It makes Perl useful only where one would use Java.

Loading Moose is certainly still slow, and even ignoring that load time, loading your class will be measurably slower if it uses Moose than plain Perl.

However, at run time, there's little to no difference.

For some jobs (CGI, simple command-line scripts, etc) load time is important, and the Moose penalty may be too great to bear. For others, such as a daemon, or a script which would take several hours to run anyway, it's a more attractive proposition.

For some jobs (CGI, simple command-line scripts, etc) load time is important, and the Moose penalty may be too great to bear. For others, such as a daemon, or a script which would take several hours to run anyway, it's a more attractive proposition.
I mostly agree. The problem is that by adding a 1000x slowdown and a mandatory 0.5s penalty, Moose turns something that is nearly free into something that has a real cost. Anything that uses Moose, directly or indirectly, has to be long-lived to absorb that cost, forcing the user to consider that cost far more often.
A 1000x slowdown should be noticeable, shouldn't it?
I compared perl -e 'eval "package A$_; sub new{bless{@_}, shift};" for 1..1e3' to perl -MMoose -e 'eval "package A$_; use Moose;" for 1..1e3'. I agree that in a long-running process, class definition times don't really matter, but on the command line, anything more than a tenth of a second matters. And for use in anything you use interactively, the difference between "I can call this with impunity" (microsends) and "I have to think whether calling this would be annoying" (tenths of seconds) is huge.

For example, let's say you want your editor to display some usage information about the function under the cursor. If the program generating that information takes a tenth of a second, you can just run it without a second thought. If, however, it takes half a second or more, you have to be much more careful.

Or maybe you want to draw a curve from the values generated by some program. If it takes milliseconds to run, you can just call it with each value. If it takes 0.5 seconds, you have to carefully consider when to call it.

If you are creating 1e3 classes in your program, then your issue isn't Moose it is a fundamental misunderstanding of how to use OOP.

But hey, it makes a good benchmark (in that many benchmarks are nothing more then a deck stacked in favor of a particular argument).

If doing x a thousand times is a thousand times slower than doing y a thousand times, then you should never do x. Got it.

Well, if "x" is constructing a million classes, then you should be safe cause no sane person would do that for any sane program. But if "x" is constructing a single class, then you have to decide if "x" being a thousand times slower then "y" (perhaps the difference between a millisecond and a microsecond as Buddy suggested) is really something that is important to you or not.

Execution speed is just one factor among many that programmers have to balance every day, if you choose your tools solely on that, you will have a pretty small set of tools.

Leave a comment

About Buddy Burden

user-pic 7 years in California, 17 years in Perl, 26 years in computers, 46 years in bare feet.