Programming with Moose/Problems solved/Type-system

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Perl lacks and Moose doesn't. At least, not nearly as much.

Back to the issues of Perl, more specifically another feature not covered by the Object System but delivered as a blessed benediction of Moose. So in this example, you're writing a program and like a good programmer, *all* of your URLs are handled by URI objects, and all of your methods operate on URI objects - or at least they think they do. Having methods dependent on URI objects is a good thing. Trust me, it is, the alternative is heaven-1. Unfortunately, when you publish the module to a world of intellectually inferior blokes you have no streamlined method of making sure they send only URIs.[1]

Again this is where Perl is lacking. All urls are stored as URI objects, but you can't easily make sure they're only URI objects.

Roots in pre-object fanaticsm[edit | edit source]

Ponder for a bit the utility of sigils. One of the side effects of sigils is a function knows at compile time whether or not the input sent to it is in the right ballpark. Perl's sigils are a little more voodoolicious because sometimes they can change the value by acting as an operator.[2] Let's look at how perl deals with validating input.

my %foo = ( foo => bar );

# This will not compile because the CORE::values function
# only operates on a hash, and not an array as indicated
# by the sigil. See perl's prototypes for more info.
print for values @foo;

In the following example, the class HashOperations expects a HashObject:

my $foo = HashObject->new;
$foo = 'stupid_mistake';

## Stupid non-intelligent error will happen here
## because the user made a stupid mistake.
HashOperations->load( $foo )->values;

So the issue here, is that the procedural non-referenced form will be caught in compile time, and all other object variants will not.

The old way[edit | edit source]

The old stock-Perl way to tackle this problem was really a joke on the users. Rather than adding specific functionality to the CORE, or radically modifying it, the core-team would rather bother the programmer with modular language add-ons that productivity is totally dependent on.[3] We can speculate on the reasoning for the lack of TypeConstraint functionality.

  • There weren't any examples of interpreted languages for Perl to model itself after, never mind interpreted languages with base-object types.
  • Perl's references came before its objects.
  • A hash is a Hash (of Scalars), an array is an Array (of Scalars). A good portion of this can be determined on compile time by the use of perl's sigils. This is not so with Objects which occur wholly at runtime, and aren't granted a unique sigil.

An Example[edit | edit source]

In this set of examples we will show how Class::Accessor is automagically inferior to Moose at making accessors, because of Moose's ability to specify valid types.[4]

Here we create a custom datatype, ie. NonMooseObject:

package NonMooseObject;
use strict;
use warnings;

use base 'Class::Accessor';

BEGIN { __PACKAGE__->mk_accessors( 'uri' ) };

sub new {
	my ( $class, $hash ) = @_;

	my $self = bless $hash || {}, $class;

	$self;

}

This is what you want your user to do:

package main;

my $uri = URI->new( 'http://moose.com' );
my $obj = NonMooseObject->new({ uri => $uri });
$obj->uri;       ## prints php.
$obj->uri->path; ## joy

This is what you don't want your user to do. In this example we show how easily a NonMooseObject can be abused. There is one important thing that is difficult to show in an example: the death might or might not occur on the call to ->new. Hopefully, for the user and programmer, it dies on the call to new; but, let's assume you run for 42 days, and then something internally calls ->path on $obj->uri when ->uri does not contain a URI object. The result is then a bug that could be much harder to troubleshoot.

package main;

my $obj = NonMooseObject->new({ uri => 'http://perl.com' });
$obj->uri;       ## prints php.
$obj->uri->path; ## dies a horrid runtime death.

The pre-Moose days[edit | edit source]

Question How did you solve this problem in Perl's pre-Moose era
Answer We downgraded, losing the utility of Class::Accessor.

We told ourselves this functionality, is more advanced and niche, isn't needed everywhere. To accomplish this functionally you much first decouple your module and C::A. So not just is this solution ugly, but now you're back at having to write accessors. Baby Jesus cried around this point in time.

sub set_stupid {
	my ( $self, $uri ) = @_;
	die "bad uri" unless ref $uri eq 'URI';
	$self->{uri} = $uri;
}

Invoking the Moose[edit | edit source]

Dredging up the past is boring and disturbing, so let's now look forward now, towards the light.

An Example[edit | edit source]

package MooseObject;
use Moose;

has 'uri' => ( is => 'rw', isa => 'URI', required => 1 );

No more examples needed. You now either send in a URI object to the constructor, or you die with an accompanying stack trace and Moose's default message telling you what you need to do.

Footnotes[edit | edit source]

  1. ^ This is just as much of an issue if you expect others to follow the principles of blackbox design. The blackbox design states you should not muck with a module's internal, ie. non-exposed, non-documented, functions. With Moose, you can easily define more of what your function does so if they decide to muck with it their job will be easier.
  2. ^ @array says 'array' is an array. Yet @$array, says dereference $array as an array. Thus '@' will either imply the datatype is an array, or it points to one and it should be coerced via dereferencing.
  3. ^ Don't be an annoying kid and point out that a TypeConstraint has nothing to do with an accessor, and if you call it bloat then my stupid-seeking missile will kill you.
  4. ^ We are of course referring to the productivity when utilizing the object oriented paradigm of perl.