别名别名为常量时无法解析

As a follow up to this question about using different APIs in a single program, Liz Mattijsen suggested to use constants. Now here's a different use case: let's try to create a multi that differentiates by API version, like this:

class WithApi:ver<0.0.1>:auth<github:JJ>:api<1>  {}
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}

multi sub get-api( WithApi $foo where .^api() == 1 ) {
    return "That's version 1";
}

multi sub get-api( WithApi $foo where .^api() == 2 ) {
    return "That's version deuce";
}

say get-api(WithApi.new);
say two.new.^api;
say get-api(two.new);

我们将常量用于第二个版本,因为两者不能在单个符号空间中在一起。但这会产生此错误:

That's version 1
2
Cannot resolve caller get-api(WithApi.new); none of these signatures match:
    (WithApi $foo where { ... })
    (WithApi $foo where { ... })
  in block <unit> at ./version-signature.p6 line 18

So say two.new.^api; returns the correct api version, the caller is get-api(WithApi.new), so $foo has the correct type and the correct API version, yet the multi is not called? Is there something I'm missing here?

评论
  • bquia
    bquia 回复

    因为评论似乎不足以讨论JJ的答案,所以我写了这篇文章,我打算将其作为对JJ和Liz的统一评论。

    Let's start with JJ's final answer (I deleted a redundant line that must be a mistake). JJ wants a way to dispatch on api that is independent of any other consideration apart from a single thing that all the relevant types share. In order to get that, they've opted to put everything into dynamic where clauses that each do two method calls to check api and name:

    class WithApi:ver<0.0.1>:auth<github:JJ>:api<1>  {}
    my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
    my constant three =  my class WithApi:ver<0.0.2>:api<1> {}
    
    multi sub get-api( $foo where .^api() == 1 &&  .^name eq "WithApi" ) {
        return "That's version 1";
    }
    
    multi sub get-api( $foo where .^api() == 2 && .^name eq "WithApi") {
        return "That's version deuce";
    }
    
    say get-api(WithApi.new); # That's version 1
    say get-api(two.new); # That's version deuce
    say get-api(three.new); # # That's version 1
    

    Liz(和我)对JJ使用动态编程而不是使用静态类型和常量感到困扰。 JJ的方法相对较慢,并且会遇到运行时错误。它在某些情况下可能有一些优点,但始终会存在严重的缺点,包括在最终答案中使用的示例JJ的情况下。

    我仍然认为JJ也应该能够吃掉Liz的蛋糕之类的东西。例如,可以引入一个mixin,它附加了可以在其上分派的静态类型:

    role WithApi[$api] {}
    constant one = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> does WithApi[1] {}
    constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> does WithApi[2] {}
    constant three = my class WithApi:ver<0.0.2>:api<1> does WithApi[1] {}
    
    multi sub get-api( WithApi[1] $foo ) {
        return "That's version 1";
    }
    
    multi sub get-api( WithApi[2] $foo ) {
        return "That's version deuce";
    }
    
    say get-api(one.new); # That's version 1
    say get-api(two.new); # That's version deuce
    say get-api(three.new); # # That's version 1
    

    这不是DRY,但它很简单,并且在编译时进行了检查和解析。

    总而言之,有时动态编程是到目前为止最好的编码方法。但是,imo,如果没有已知的基本不利因素,那么任何看起来要解决一些一般问题的方法最好都是简单静态的。

  • 环嘉玉
    环嘉玉 回复

    解决方案非常简单:还为别名“ 1”版本:

    my constant one = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
    my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
    
    multi sub get-api(one $foo) {
        return "That's version 1";
    }
    
    multi sub get-api(two $foo) {
        return "That's version deuce";
    }
    
    say one.new.^api;     # 1
    say get-api(one.new); # That's version 1
    say two.new.^api;     # 2
    say get-api(two.new); # That's version deuce
    

    And that also allows you to get rid of the where clause in the signatures.

    请注意,您将无法通过它们的名字来区分它们:

    say one.^name;  # WithApi
    say two.^name;  # WithApi
    

    如果您希望这样做,则必须设置与该类关联的元对象的名称:

    my constant one = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
    BEGIN one.^set_name("one");
    my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
    BEGIN two.^set_name("two");
    

    然后,您将可以按名称区分:

    say one.^name;  # one
    say two.^name;  # two
    
  • bjstry
    bjstry 回复

    Elizabeth Mattijsen answer above game me a hint. Signatures match symbol, not symbol name. However, when you alias (using a constant) to a new name, you still keep the name. Let's use this to have an uniform multi call where the only thing that changes is the api version:

    class WithApi:ver<0.0.1>:auth<github:JJ>:api<1>  {}
    my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
    my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
    my constant three =  my class WithApi:ver<0.0.2>:api<1> {}
    
    multi sub get-api( $foo where .^api() == 1 &&  .^name eq "WithApi" ) {
        return "That's version 1";
    }
    
    multi sub get-api( $foo where .^api() == 2 && .^name eq "WithApi") {
        return "That's version deuce";
    }
    
    say get-api(WithApi.new); # That's version 1
    say get-api(two.new); # That's version deuce
    say get-api(three.new); # # That's version 1
    

    Again following Elizabeth's answer in the previous question, constants are used for the new versions to avoid namespace clashes, but the multis will be selected solely based in api version in a relatively type-safe way, without needing to use the aliased symbols in the signature. Even if you invent a new constant to alias WithApi with any metadata, the multi will still be selected based on api version (which is what I was looking for).

  • ad88282284
    ad88282284 回复

    给定名称空间中只能有一件事情。

    I assume the whole reason you are putting the second declaration into a constant and declaring it with my is that it was giving you a redeclaration error.

    事实是,它仍然应该给您一个重新声明错误。 您的代码甚至不应该编译。

    You should have to declare the second one with anon instead.

    class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
    constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
    

    It would then be obvious why what you are trying to do doesn't work. The second declaration is never installed into the namespace in the first place. So when you use it in the second multi sub it is declaring that its argument is the same type as the first class.

    (Even when you are using my in your code it isn't managing to install it into the namespace.)

    您假设名称空间是平面名称空间。 不是。

    您可以拥有一个只有一个名称的类,但是只能使用另一个名称进行访问。

    our constant Bar = anon class Foo {}
    
    sub example ( Bar $foo ) {
        say $foo.^name; # Foo
    }
    example( Bar );
    

    为方便起见,Raku将该类安装到名称空间中。 否则,将有很多看起来像这样的代码:

    our constant Baz = class Baz {}
    

    您正在尝试使用名称空间,同时尝试颠覆名称空间。 我不知道您为什么期望它能起作用。

    一种让您的确切代码在编写时正常工作的快速方法是,声明第二个类是第一个类的子类。

    class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
    constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> is WithApi {}
    #                                                                   ^________^
    

    Then when the second multi checks that its argument is of the first type, it still matches when you give it the second.

    不好

    确实没有内置的方法可以完全执行您想要的操作。

    您可以尝试创建一个新的元类型,该新的元类型可以创建一个将充当两个类的新类型。

    我个人只是将它们都别名为独立名称。

    constant one = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
    constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
    

    如果要从模块加载它们:

    constant one = BEGIN {
       # this is contained within this block
       use WithApi:ver<0.0.1>:auth<github:JJ>:api<1>;
    
       WithApi # return the class from the block
    }
    constant two = BEGIN {
       use WithApi:ver<0.0.1>:auth<github:JJ>:api<2>;
       WithApi
    }