Pages: Welcome | Projects

Lazy Perl

2015/7/28
Tags: [ Ideas ] [ perl ]

Perl's OO framework, Moose, has got a very nice feature that is Lazy Attributes. Regular ones are assigned by construction: they can be checked automatically to be explicitly set (required), and they can be undefined only if allowed by the type (Maybe[T] can be undefined, while T must be defined). If not required, attributes can be declared as lazy, and an automatic procedure can be defined for when they are accessed, so they get automatically generated upon the first access.

But what if you are not using Moose?

Moose is extremely fancy, but not always what you need. Sometimes OO is an overkill, and you simply want plain old perl. Still you would like to get some lazyness.

Say for instance we have a command with different operating modes foo, bar and baz. The first two modes need a particular data structure named $magic to be in place, which is not required in the third mode. The $magic structure is generated by the load_magic function, which is expensive.

A simple way of getting it done is of course the following:

my $magic

sub load_magic {
    my %out;
    #... here monkeys ...
    $out{huge} = $huge;
    $out{slow} = $slow;
    return \%out;
}

if (mode eq 'foo') {
    $magic = load_magic;
    foo($magic->{huge}, $magic->{slow});
} elif (mode eq 'bar') {
    $magic = load_magic;
    # Do other stuff with $magic
    bar($magic->{huge}, $magic->{slow});
} elif (mode eq 'baz') {
    # Do more stuff, no $magic involved
    baz();
}

This works just nice, and it's also easy to understand.

Things may however get complicated. What if instead of mutually exclusive modes we have optional chunks of execution?

if ($required{foo}) {
    $magic = load_magic;
    foo($magic->{huge}, $magic->{slow});
}

if ($required{bar}) {
    $magic = load_magic unless defined $magic;
    bar($magic->{huge}, $magic->{slow});
}

if ($required{baz}) {
    baz();
}

…Here it's still simple, but we have got to keep duplicating the conditional.

Equivalent but lazy: merge $magic into load_magic with a state variable:

use feature qw/state/;

sub magic {
    state $magic = do {
        my %out;
        # ... here monkeys ...
        $out{huge} = $huge;
        $out{slow} = $slow;
        \%out;
    }
}

The state feature will help us in keeping the state and initializing it once. The do block will be therefore executed just on the first time magic is called, while subsequent calls will just run the remaining part of the magic function, that is …nothing.

A function not using return will implicitly return the last computed value, which here is $magic.

In a sense, it's like the magic function is a simple Object with only one method. It keeps the context. It can also be seen as a singleton in Java.

Now we can simply accomplish our task with three simple lines:

foo(magic->{huge}, magic->{slow}) if $required{foo};
bar(magic->{huge}, magic->{slow}) if $required{bar};
baz if $required{baz};

Simple and elegant.