如何指定包含我接受的所有内容并排除其他所有内容的Perl 6签名?

时间:2017-06-12 04:55:57

标签: signature perl6

我在这个问题中的假设是我指定的签名完全描述了方法将接受的所有内容。显然我错了,但无论如何我想得到它。如果我没有在签名中指定它,我不希望其他人能够提供它作为一个认为它会做某事的论据。

我为方法创建一个带有单个位置参数的签名,我希望它只接受单个位置参数。但是,它也接受命名参数而没有投诉:

class SomeClass {
    method something ( Int $n ) { 'Ran something' }
    }

put SomeClass.something: 137; # Ran something
put SomeClass.something: 137, :foo('bar'); # Ran something

但是,如果我定义一个采用命名参数的方法,那么每次我定义它时都会调用它。此外,虽然我认为我说它需要一个命名参数foo,但它不是必需的,它仍然接受我没有指定的命名参数:

class SomeClass {
    multi method something ( Int $n ) { 'Ran something' }
    multi method something ( Int $n, :$foo ) { "Ran $foo" }
    }

put SomeClass.something: 137;  # Ran
put SomeClass.something: 137, :foo('bar'); # Ran bar
put SomeClass.something: 137, :bar('foo'); # Ran

所以,有些问题:

  • 如何指定包含我想接受的所有内容的签名并排除其他所有内容?

  • 如何强制Perl 6选择最接近的匹配签名?

  • Perl 6决定检查方法的顺序是什么?

3 个答案:

答案 0 :(得分:10)

您正在触及有关多方法调度最难掌握的事情之一。最重要的是要知道,如果没有指定,每个 method签名都有一个隐式*%_(也就是一个邋hash的哈希)。这意味着它将吃掉任何非特定的命名参数。

class A {
    method a() { dd %_ }  # %_ exists even if not specifically specified
}
A.a(:foo)   # {:foo}

您需要意识到的第二件事是,命名参数只能起到打破平局的作用。因此,如果有多个候选人具有相同的位置参数匹配集,MMD将使用第一个有效的候选人(考虑到所有意外的命名参数都被%_所淹没):

class A {
    multi method a(:$foo) { say "foo" }
    multi method a(:$bar) { say "bar" }
}
A.a(:bar)    # foo

这个看似意外的结果是由以下事实引起的:

  1. 两个候选人具有相同数量的位置参数
  2. 第一个候选匹配,因为:$foo是可选的
  3. 并且:bar被隐含的*%_
  4. 吃掉了

    为了使其更像您期望的工作,您需要将需要打破的候选人放入您希望他们触发的顺序中,并强制命名任何命名参数:

    class A {
        multi method a(:$foo!) { say "foo" }
        multi method a(:$bar!) { say "bar" }
    }
    A.a(:bar)    # bar
    

    如果候选人有多个命名参数,那么很快就会变得相当复杂,只要拥有一个使用%_内省的方法,你可能会更好:

    class A {
        method a() {
            if %_<foo> {
                say "foo"
            }
            elsif %_<bar> {
                say "bar"
            }
            else {
                die "None of the signatures matched"
            }
        }
    }
    A.a(:bar)    # bar
    

    希望这会让事情更加清晰: - )

答案 1 :(得分:5)

方法总是带有隐式*%_参数,cf

say method {}.signature #=> (Mu $: *%_)

这是设计理念,认为子类可以挑选出他们喜欢的参数,然后通过nextsame等重新发送(参见design docs - 不知道在哪里,甚至是不是'{3}}。在其他地方记录了。)

有几种方法可以拒绝未声明的命名参数,例如通过 where 子句

method m($positional, :$explicit, *% where !*) { ... }

或通过空的子签名

method m($positional, :$explicit, *% ()) { ... }

当传递非声明的参数时,前者将失败并带有

Constraint type check failed in binding to parameter '<anon>'

和后者

Unexpected named argument '...' passed in sub-signature

我会留下多方法调度的确切语义供其他人回答,但我的启发式经验法则是确保所需的命名参数被声明为(即!存在)并且把更一般的方法放在最后。

答案 2 :(得分:1)

我认为Int $n, :$foo是比Int $n更具体的签名。 您可以强制foo

class SomeClass {
    multi method something ( Int $n, ) { "Ran something" }
    multi method something ( Int $n, :$foo! ) { "Ran $foo" }
}

或第一个签名更严格

class SomeClass {
    multi method something ( Int $n, *% where "foo" !~~ *) { "Ran something" }
    multi method something ( Int $n, :$foo ) { "Ran $foo" }
}