如何在Perl 6中向现有类添加方法?

15

Int类有一个is_prime方法,所以我想,只是为了娱乐,我想为我的一些涉及数论的爱好项目中的Int添加一些其他方法。

我认为我可以像这样做:

class Int {
    method is-even (Int:D $number ) returns Bool:D {
        return False if $number % 2;
        return True;
        }
    }

say 137.is-even;

但那样行不通:

===SORRY!===
P6opaque: must compose before allocating

我不知道这是否意味着我不能做那个或者我正在错误地做它。

我可以轻松地创建一个从 Int 继承的新类,但这并不是我感兴趣的:

class MyInt is Int {
    method is-even () returns Bool:D {
        return False if self % 2;
        return True;
        }
    }

my $n = MyInt.new(138);
say $n.is-even;

我不在寻找变通方案或替代解决方案。

4个回答

16

有一个语法糖可以实现这个功能 - augment

use MONKEY-TYPING;

augment class Int {
    method is-even() returns Bool:D {
        return False if self % 2;
        return True;
    }
}

出于两个原因考虑,对类进行扩充被认为是危险的:首先,远距离操作;其次,因为(据我所知),存在使各种方法缓存处于无效状态的非最优化潜在风险。

因此,在允许使用它之前,需要提供MONKEY-TYPING编译指示。

另外,请注意,is-even可以更紧凑地编写为self %% 2


@briandfoy:请注意,我将“未定义行为”改为了“去优化”——默认情况下,类应该是开放的;据我所知,类的最终实现尚未实现。 - Christoph

6

哦,这个方法可行,我之前以为试过了。比我在问题中提出的更好。

Int.^add_method( 'is-even', method () returns Bool:D {
    return False if self % 2;
    return True;
    } );

say 137.is-even;

我不确定这样做是否有效。 add_method文档说我们只应在类型组合之前执行此操作。 如果我调用Int.^methods,则is-even不会显示出来。 但是,它似乎可调用并执行正确的操作。

词法方法

进一步尝试后,我发现可以创建一个没有附加到任何类的方法,并在对象上调用该方法:
my &is-even = method (Int:D :) returns Bool:D { self %% 2 };

这里构造了一个 Callable (查看 &is-even.WHAT)。在签名中,我将其限制为明确的整数类型值(Int:D),但没有给它一个名称。我在类型约束后添加冒号以指示第一个参数是调用者。现在我可以将该方法应用于任何我喜欢的对象:

say 137.&is-even;
say 138.&is-even;
say "foo".&is-even;  # works, although inside is-even blow up

在不同维度看来,这很好因为它是词法的,但不好的是可能会有错误类型的对象调用它。错误会在认为有方法可分派后出现。


1
Int 上另外调用 .^compose(或至少是 .^publish_method_cache)可能是一个好主意,也可能不是。 - Christoph
这个在哪里有文档记录? - brian d foy
2
我认为这并没有记录下来,而且我甚至不能百分之百确定我的建议是否正确——只是因为Perl6::Metamodel::MethodContainer.add_method使用参数0调用了nqp::setmethcacheauth,而Perl6::Metamodel::ClassHOW.compose则调用了Perl6::Metamodel::MROBasedMethodDispatch.publish_method_cache,后者再次设置了nqp::setmethcacheauth - Christoph
关于编辑,你的Rakudo版本是什么?我的最近版本在调用“&is-even”时会出现“Type check failed in binding <anon>; expected Int but got Str”的错误,这是因为它无法将“foo”转换为整数。 - Christoph
我得到了相同的错误。我宁愿得到类似“没有这样的方法”的东西。我对%%的评论来自我测试中发生的其他事情。 - brian d foy
显示剩余2条评论

4
您可以像调用方法一样调用子程序,只需在名称前面加上 & 符号:
sub is-even(Int $n) { $n %% 2 }
say 4.&is-even;  # True

当然,这只是语法糖,但当它比直接调用子例程更可读时,我已经用过几次。

(我知道你“不寻求解决方法或替代方案”,但你也发布了一些,所以我想,为什么不呢?)


我在我的第一个答案中已经注意到了这一点。 :) - brian d foy
但是你使用了一个有趣的方法赋值,而不是简单地声明一个子程序。 - mscha

3

如果您仅需要对类的某些实例执行此操作,则有另一种有趣的方法。 您可以为对象添加一个角色:

 my $decorated = $object but role { ... }

这就是我一直在寻找的:向实例添加方法。我猜对象的类必须能够进行clone,因为我得到了这个错误信息:No such method 'clone' for invocant of type 'Perl6::Grammar+{Nogil::NogilGrammar}' - Tinmarino
我明白了,它就是我一直在寻找的混合物。 - Tinmarino

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接