beasy
beasy

Reputation: 1227

How to write lazy accessors

What is the best way to build attributes lazily?

class I {
    has $!cheezeburger;

    method cheezeburger {
        given $!cheezeburger {
            when .so {return $_}
            default { 
                # build $cheezeburger, set attribute to it, return 
            }
        }
    }
}

That's a lot of cheezeburger. What might be a less verbose way?

Upvotes: 6

Views: 232

Answers (3)

raiph
raiph

Reputation: 32404

Existing modules

There are two lazy attribute modules.

Ordinary code

Brad's $!cheezeburger //= do { ... }; seems like a fairly straight-forward solution that'll suffice for many use cases.

Something better?

You may find that #perl6 folk want or can provide something better.

The most recent serious #perl6 discussions I'm aware of about lazy attribute initialization happened during 2015 on May 5th, 7th, 20th and June 5th, 8th, and 20th. Search for "will lazy" in pages of #perl6 log with at least one "will lazy" match. The TL;DR of these discussions is that rjbs, mst, and other Moose users were used to nice lazy attribute initialization and a solution was added to Rakudo; it was then backed out because masak and others thought it had issues and they thought nice solutions could be created in module space and then moved back in to core if/when that seemed wise.

Upvotes: 5

Christoph
Christoph

Reputation: 169573

The pragmatic solution given by Brad that initializes the attribute if it is undefined should be good enough for many cases:

class Foo {
    has $!cheezeburger;
    method cheezeburger {
        $!cheezeburger //= do { ... }
    }
}

Another approach would be using does to replace the accessor method by mixing in a role during its first call, using black magic (aka NQP ops) to access the private attribute:

class Foo {
    has $!cheezeburger;
    method cheezeburger {
        self does role {
            method cheezeburger {
                use nqp;
                nqp::getattr(self, Foo, '$!cheezeburger');
            }
        }
        $!cheezeburger = do { ... }
    }
}

Upvotes: 3

user5854207
user5854207

Reputation:

class A {
    has $!lazy;
    method BUILD { $!lazy := Nil };
    method lazy { $!lazy := (my $a = 42) if $!lazy =:= Nil; $!lazy }
};
my $a = A.new;
say [$a.lazy, $a.lazy];

If $!lazy is meant to hold undefined values you need to hop through a few loops. First we bind Nil to $!lazy to hold a value a container should not be able to hold. If $!lazy is still bound to Nil we create a new container and assign a value to it. If the value is immutable, you don't need the extra container. Any type constraint you need on $!lazy need to be on $a because constraints are a property of a container not the variable/class-property.

Upvotes: 4

Related Questions