oop intermediate

How to use lazy attribute builders in Perl Moo?

Question

How to use lazy attribute builders in Perl Moo?

In Perl's Moo object system, a lazy attribute builder is a powerful technique to defer the initialization of an attribute's value until it is actually needed. This can improve performance by avoiding expensive computations or I/O until necessary, and helps enforce separation of concerns by keeping default-building logic separate from simple default values.

What is a lazy attribute in Moo?

In Moo, attributes are defined using has. By default, if you provide a default, it is set immediately when an object is constructed. But if you want the attribute to be built only when first accessed, you mark the attribute with lazy => 1. This tells Moo not to construct the attribute until it's needed.

Builder Methods: the key to lazy attributes

A builder is a method that returns the default value for the attribute. You specify which method to use as the builder with the builder option when defining the attribute. Commonly, builders are named with a leading underscore for convention, e.g. _build_attribute_name.

When you combine lazy => 1 with a builder, Moo waits until the attribute is first accessed, then calls the builder method once to populate it.

Example: Lazy attribute builder in Moo


use strict;
use warnings;
use feature 'say';

{
    package MyClass;
    use Moo;

    has 'expensive_data' => (
        is      => 'ro',
        lazy    => 1,
        builder => '_build_expensive_data',
    );

    sub _build_expensive_data {
        say "Building expensive_data...";
        # Simulate expensive operation
        return [1..5];
    }
}

my $obj = MyClass->new();

say "Object created.";

# At this point, _build_expensive_data has not been called yet.

say "Accessing expensive_data first time:";
my $data = $obj->expensive_data;
say "Data: @{ $data }";

# Subsequent access will NOT call the builder again
say "Accessing expensive_data second time:";
$data = $obj->expensive_data;
say "Data: @{ $data }";
    

Running this script outputs:


Object created.
Accessing expensive_data first time:
Building expensive_data...
Data: 1 2 3 4 5
Accessing expensive_data second time:
Data: 1 2 3 4 5
    

Key points and gotchas

  • lazy => 1: Ensures the attribute is not built during construction, but on demand.
  • builder => 'method_name': Points to the method that builds the default value. The builder is invoked once and its result cached.
  • Builder naming convention: It is idiomatic to name the builder method _build_attribute_name, but you can use any method name.
  • Must not combine default and builder: Use either default for simple values or builder for lazy construction, not both.
  • Read-only vs read-write: Lazy attributes often make sense as is => 'ro', though it's not mandatory.
  • Context: Builders receive the instance ($self) as the first argument.
  • Thread safety / cloning: The laziness applies only to when the attribute is accessed for the first time on that object.

Version considerations

The lazy attribute builder approach has been part of Moo since its initial versions, so it works on virtually all releases of Moo compatible with Perl 5.8 and above. For enhanced features and attribute traits, consider MooX extensions or Moose, but for most use cases, this lazy building pattern works efficiently and clearly.

Summary

Lazy attribute builders in Moo are a neat way to defer expensive or contextual attribute initialization until the value is actually required. By specifying lazy => 1 and a builder method, you tell Moo to generate the attribute value on-demand. This improves performance and code clarity, especially when initializing attributes with complex or costly defaults.

Verified Code

Executed in a sandbox to capture real output. • v5.34.1 • 37ms

Tip: edit code and use “Run (Browser)”. Server runs always execute the published, verified snippet.
STDOUT
Object created.
Accessing expensive_data first time:
Building expensive_data...
Data: 1 2 3 4 5
Accessing expensive_data second time:
Data: 1 2 3 4 5
STDERR
(empty)

Was this helpful?

Related Questions