新方法中的默认值导致对象构造以意外的方式工作

Ste*_*ieD 9 raku

有人可以帮助我理解以下行为吗?

class Box {
    has $.data;

    multi method new($d) {
        say  'here';
        self.bless(data => $d);
    }
}

# construct object with the custom new()
my $box = Box.new('hi');
say $box.data;

# construct object using default new()
my $box2 = Box.new(data => 'be');
say $box2.data;
Run Code Online (Sandbox Code Playgroud)

这输出:

here
hi
be
Run Code Online (Sandbox Code Playgroud)

好的,完美,正是预期的那样。但是,更改代码,以便新方法$d具有如下所示的默认值:

class Box {
    has $.data;

    multi method new($d = '') {   # we give $d a default value now
        say  'here';
        self.bless(data => $d);
    }
}

my $box = Box.new('hi');
say $box.data;

my $box2 = Box.new(data => 'be');
say $box2.data;
Run Code Online (Sandbox Code Playgroud)

您现在得到以下输出:

here
hi
here  # new method is getting called both times and $.data is not set
Run Code Online (Sandbox Code Playgroud)

这不是我所期望的。我想我会得到与以前相同的输出。有人可以解释一下为什么我没有得到相同的输出吗?

更新:我注意到,如果我将 new() 签名更改为:

multi method new($d = '', *%_ ())

我可以让事情按预期进行。*%_ ()但我仍然不明白为什么它一开始就不起作用。

Sil*_*olo 8

这里考虑的两个重载是您的重载,以及在顶级类型new上定义的一个重载。即,Mu

multi method new($d)
multi method new(*%attrinit)
Run Code Online (Sandbox Code Playgroud)

或者,更明确地写为,

multi method new(Box: $d?)
multi method new(Mu: *%attrinit)
Run Code Online (Sandbox Code Playgroud)

但是,我们需要更加明确。因为,虽然看起来只有后者应该匹配Box.new(data => 'be'),但实际上两者都是有效的候选者。这是因为,根据 的文档Method

方法会自动将额外的命名参数捕获到特殊变量中%_,其他类型的例程将在运行时抛出异常。所以

method x() {}
Run Code Online (Sandbox Code Playgroud)

实际上相当于

method x(*%_) {}
Run Code Online (Sandbox Code Playgroud)

这也适用multi method于。其背后的基本原理是允许方法将它们不理解的命名参数转发给调用者。

所以,实际上,我们的两个重载是

multi method new(Box: $d?, *%_)
multi method new(Mu: *%attrinit)
Run Code Online (Sandbox Code Playgroud)

因此,当我们编写 时Box.new(data => 'be'),我们有两个有效的候选者,第一个有一个更具体的调用者(Box而不是Mu),因此它被调用。

在没有默认参数的情况下,候选人看起来像

multi method new(Box: $d, *%_)
multi method new(Mu: *%attrinit)
Run Code Online (Sandbox Code Playgroud)

multi因此,如果有一个位置参数,第一个只是调用的候选者。

我们可以使用Stack Overflow 答案中的技巧来抑制这种行为。

multi method new($d = '', *% ()) {
    say  "here $d";
    self.bless(data => $d);
}
Run Code Online (Sandbox Code Playgroud)

(这里的空间*% ()很重要)实际上是一个相当巧妙的小技巧。链接的答案比我能更好地解释它,但基本上该*%部分说“我接受任何命名参数”,然后()是一个要匹配的子签名,即空签名。您无法阻止方法接受命名参数,因此这或多或少读作“我的方法接受任何命名参数,只要命名参数列表等于空列表”。


p6s*_*eve 5

有趣的问题和好的答案已经。

然而,我认为两者都有点过多地依赖于 raku 的晦涩方面,即“bless”和“*% ()”——并不是说这些技巧没有一席之地,而是给出的常见情况(a默认位置)不需要您伸手去拿电动工具。

这是我的解决方案:

class Box {
    has $.data = '';            # we give data a default value here

    multi method new($data) {   
        samewith(:$data)        # redespatch positional data as named data
    }   
}
Run Code Online (Sandbox Code Playgroud)

  • 那真是太好了。假设OP确实只想将位置参数转换为以“new”命名,这是一种非常干净的方法。 (2认同)