如何编写惰性访问器

How to write lazy accessors

懒惰地构建属性的最佳方法是什么?

class I {
    has $!cheezeburger;

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

芝士汉堡太多了。什么可能是不那么冗长的方式?

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];

如果 $!lazy 是为了保存未定义的值,您需要跳过几个循环。首先,我们将 Nil 绑定到 $!lazy 以保存容器不应保存的值。如果 $!lazy 仍然绑定到 Nil 我们创建一个新容器并为其分配一个值。如果值是不可变的,则不需要额外的容器。您需要在 $!lazy 上的任何类型约束都需要在 $a 上,因为约束是容器的 属性 而不是 variable/class-property.

如果属性未定义,Brad 给出的初始化属性的实用解决方案在许多情况下应该足够好:

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

另一种方法是使用 does 来替换访问器方法,方法是在第一次调用期间混入一个角色,使用黑魔法(又名 NQP ops)访问私有属性:

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

现有模块

有两个lazy attribute modules

普通码

Brad 的 $!cheezeburger //= do { ... }; 似乎是一个相当直接的解决方案,足以满足许多用例。

有更好的吗?

您可能会发现 #perl6 用户想要或可以提供更好的东西。

据我所知,最近一次关于惰性属性初始化的#perl6 严肃讨论发生在 2015 年的 5 月 5 日、7 日、20 日和 6 月 5 日、8 日和 20 日。在 pages of #perl6 log with at least one "will lazy" match 中搜索“will lazy”。这些讨论的 TL;DR 是 rjbs、mst 和其他 Moose 用户习惯于 nice lazy 属性初始化,并在 Rakudo 中添加了解决方案;然后它被取消了,因为 masak 和其他人认为它有问题,他们认为可以在模块 space 中创建好的解决方案,然后移回核心 if/when 这似乎是明智的。