Modern OOP with Moo/Moose

Perl Intermediate/Advanced — Modern OOP with Moo/Moose
Perl Intermediate/Advanced
Course 2 · Chapter 4 · Modern OOP with Moo/Moose

✨ Modern OOP with Moo/Moose

Chapter 3 showed the manual bless/@ISA approach still common in legacy code. This chapter shows what most new Perl OOP code actually reaches for today: Moo and Moose, both of which eliminate nearly all of the boilerplate — hand-written accessors, manual constructors, manual @ISA assignment — the previous chapter required by hand.

⚖️ Moose vs. Moo — Which One?

MooseMoo
Feature setThe original, full-featured, built on a complete meta-object protocolA lightweight, largely Moose-compatible subset
Startup costHeavier — more dependencies, slower to loadMinimal dependencies, fast startup
Typical useWhen you need Moose's deeper introspection/meta-programming featuresThe common default for most modern code

This chapter uses Moo for its examples, since it's the more commonly reached-for modern default — the attribute/inheritance syntax shown below is nearly identical in Moose.

🏷️ Defining Attributes

package Animal; use Moo; has 'name' => (is => 'ro'); # read-only — generates a getter only has 'age' => (is => 'rw'); # read-write — generates a getter AND a setter

has replaces every hand-written accessor from Chapter 3's $self->{name} pattern — declaring an attribute automatically generates a method of the same name. is => 'ro' gives you $animal->name (read-only); is => 'rw' additionally lets you call $animal->age(29) to update it. No manual hashref key access anywhere in your own code.

🏗️ The Automatic Constructor

my $rex = Animal->new(name => "Rex", age => 3); say $rex->name; # Rex — no hand-written new() needed at all

Moo generates new for you — no manually shift-ing the class name and building a hashref by hand, per Chapter 3's constructor pattern. Attributes accept two more useful options:

has 'name' => (is => 'ro', required => 1); # new() dies if "name" isn't supplied has 'legs' => (is => 'ro', default => sub { 4 }); # default value if not supplied
⚠ default Must Be a Coderef for Anything Mutable
has 'tricks' => (is => 'rw', default => []); # WRONG — errors, or shared across instances has 'tricks' => (is => 'rw', default => sub { [] }); # correct — a fresh arrayref built per instance

This is the exact same root cause as Course 1, Chapter 2's mutable-default-argument gotcha, resurfacing in a new form: default => [] would build one arrayref when the attribute is declared, shared by every instance that doesn't supply its own — modifying it on one object would leak into every other. Wrapping it in sub { [] } defers building the default until each individual object is actually constructed, giving every instance its own independent, fresh reference.

🧬 Inheritance with extends

package Dog; use Moo; extends 'Animal'; # replaces Chapter 3's manual: our @ISA = ('Animal'); sub speak { my $self = shift; return $self->name . " says Woof!"; }

extends 'Animal'; is the entire inheritance declaration — considerably cleaner than manually assigning @ISA, and Dog still inherits Animal's attributes (name, its generated accessor, its constructor logic) automatically.

🎭 Roles — Composition Over Inheritance

package Loud; use Moo::Role; # a ROLE, not a class — no "new", meant to be composed in sub speak_loudly { my $self = shift; return uc($self->speak()); } package Dog; use Moo; extends 'Animal'; with 'Loud'; # composes the Loud role's methods directly into Dog

A role (with 'Loud';) is genuinely different from inheritance — it's a bundle of methods composed directly into a class, rather than inherited from a parent up a chain. This sidesteps a lot of the awkwardness multiple inheritance can create (Course 3's Vim course, and the site's own OOP-focused chapters elsewhere, cover this same composition-over-inheritance instinct from other angles) — a class can compose several unrelated roles together without any of the ambiguity a shared parent class hierarchy might introduce.

🔧 A Worked Example — Rewriting Chapter 3's Animal Hierarchy

package Animal; use Moo; has 'name' => (is => 'ro', required => 1); sub speak { my $self = shift; return $self->name . " makes a sound"; } package Dog; use Moo; extends 'Animal'; sub speak { my $self = shift; return $self->name . " says Woof!"; } package main; my $rex = Dog->new(name => "Rex"); say $rex->speak(); # Rex says Woof!

Compare this directly to Chapter 3's hand-rolled version: no manual new, no manual hashref-building, no manual @ISA assignment, no hand-written accessor for name — every piece that was pure boilerplate before is now generated by use Moo;, has, and extends, leaving only the logic that's genuinely specific to each class.

Moo/Moose Attributes vs. Python's @dataclass

The site's own Python Advanced course closes its capstone with @dataclass — auto-generating __init__, __repr__, and __eq__ from type-hinted attributes. Moo's has aims at genuinely the same problem (eliminate boilerplate for a data-holding class) but goes further by default: accessors, a full constructor with required/default handling, and — via extends/with — inheritance and composition, all from the same declarative style. Different language, same underlying instinct: declare what an object holds, let the framework generate the mechanical code around it.

has

Declares an attribute; is => 'ro'/'rw' controls getter/setter generation.

Automatic new

No manual constructor — required/default handle validation and defaults.

extends

Clean inheritance declaration, replacing manual @ISA assignment.

Roles (with)

Compose methods into a class directly — genuinely different from inheritance.

💻 Coding Challenges

Challenge 1: A Moo Class

Write a Moo class Book with a required, read-only title attribute and a read-write read attribute defaulting to false. Create an instance without setting read, then update it to true using the generated setter.

→ Solution

Challenge 2: Fix the Mutable Default

Given a buggy has 'tags' => (is => 'rw', default => []);, rewrite it correctly per this chapter's warn-box, and explain in one sentence why the original version is dangerous.

→ Solution

Challenge 3: A Role

Write a Moo role Describable with a method summary that returns "This is a " . ref($self). Compose it into a class Widget using with, and call summary on an instance.

→ Solution

🎯 What's Next

Next chapter: Modules & CPANuse/require, writing your own module, and the CPAN ecosystem.