I've been writing the Moose section of the Modern Perl book for the past week. Stevan (and other people) suggested that I explain how to create and use objects in Perl with Moose before explaining the bare-bones blessed reference approach. They were right.
I'm assuming that readers don't necessarily have the theoretical understanding of how objects work and why, of why Liskov substitutability is important, of what allomorphism means, and why polymorphism and encapsulation are much more interesting than inheritance. I don't even assume that readers know any of those words.
Yet I've noticed something far more interesting.
The standard approach to teaching Perl 5 OO (at least, the one approach I've seen that works) builds on the Perl 5 implementation ideas. That is to say, "a class is a package, a method is a subroutine, and an object is a blessed reference". If you know how to work with references in Perl 5, you can use Perl 5 objects.
That's true in the sense that a blessed hash reference is still a hash reference. That's false in the sense that treating an object as a struct with a vtable pointer is a terrible way to write robust OO. (I should know; I've been hip deep in multiple implementations.)
I like a lot of things about Moose, but what I appreciate most from a didactic standpoint is that I can explain object attributes:
{
package Cat;
use Moose;
has 'name', is => 'ro', isa => 'Str';
has 'diet', is => 'rw', isa => 'Str';
has 'birth_year', is => 'ro', isa => 'Int',
default => (localtime)[5] + 1900;
}
... and it doesn't occur to readers that they can poke into a
Cat
instance directly (even though they can). Moose encourages
people to do the right thing by using accessors and respecting encapsulation
and polymorphism and allomorphism and substitution by making something
different--encapsulated access to instance data--look different from the
well-understood mechanism of its implementation.
Objects may still be blessed hashes, but users treat them differently because they have different expectations.
In writing the examples for this chapter, I changed the implementation of the class to make correctness easier (and to discuss the value of immutable objects). The refactoring was trivial, thanks to Moose features, but the interface of the class could stay the same, thanks to Moose's subtle encouragement to program to an encapsulated interface.
I always enjoy encountering such a serendipity in code, and I made sure to mention it in the book. The Perl world needs more such serendipities.
That's something I love to see. When you have code which makes the right thing easy, it's such a good way to go. In fact, I'd argue that code which sometimes does the wrong thing but overwhelmingly encourages the right thing is better than "correct" code which is hard to use. Time and time again I hear people say "but, but, but X is right!" And it may be the case that X is right, but if X is too hard to achieve, then people will screw up. It's better to succeed at being good than to fail at being perfect. Regrettably, it's a hard-learned lesson for many (I have the scars to prove it as it took me a long time to appreciate it).
Moose++.
Do you feel at all like "correctness" may just be a euphemism for doing what the OO giants (C++, Java, etc.) do by default?
I love using OO in C++ because it has a very comfortable feel for me and I think it's implemented in a well-thought-out and intelligent fashion, but when I use Moose, I feel like I'm using a hack that doesn't do justice to the flexible functionality and spirit of the underlying language.
@colinwetherbee: New Java programmers are constantly bit by the fact that public properties can be set without validation. C++ (like Java) offers plenty of ways to violate encapsulation and it takes a diligent programmer to be aware of pitfalls (in C++, it's *real* fun to discover that a subclass has just made your protected function public).
The point chromatic was making was that good code/software encourages correct behavior. If you *want* to get into a language war, we could spend plenty of time in useless nitpicking of the different languages. That's a waste of time. Instead, when looking at an individual language, see what can be done to make the most of it.
I don't see that at all, Colin. By "correctness" here I mean the use of design principles that allow maintenance and further development and use of the system in whole or in part while minimizing the scope of necessary changes.
With that definition, it's easy to say that changing the internal representation of an object (in my example, turning age() from a simple accessor to a method which uses a new accessor) shouldn't require changes to any other class or entity in the system.
Certainly Perl is flexible enough that I could have performed some magic with tied hashes... but that's a hack.