Подпись не может быть разрешена, когда она связана с константой - PullRequest
6 голосов
/ 06 апреля 2020

В качестве продолжения вплоть до этот вопрос об использовании различных API в одной программе , Лиз Маттийсен предложила использовать константы . Теперь вот другой вариант использования: давайте попробуем создать multi, который различается по версии API, например:

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

Итак, say two.new.^api; возвращает правильную версию API, вызывающий абонент get-api(WithApi.new), поэтому $foo имеет правильный тип и правильную версию API, но мульти не называется? Я что-то упускаю здесь?

Ответы [ 4 ]

6 голосов
/ 08 апреля 2020

TL; DR Ответ JJ - это предложение where во время выполнения, которое вызывает пару методов для рассматриваемого аргумента. Ответы всех остальных выполняют ту же работу, но с использованием конструкций времени компиляции, которые обеспечивают лучшую проверку и намного лучшую производительность. Этот ответ сочетает в себе мой взор с ответами Лиз и Брэда.

Ключевые сильные и слабые стороны ответа JJ

В ответе JJ все логики c содержатся внутри предложения where. Это его единственная сила относительно решения в ответах всех остальных; он вообще не добавляет Lo C.

Решение JJ имеет два существенных недостатка:

  • Проверка и издержки отправки для предложения where для параметра: понесенный во время выполнения 1 . Это дорого, даже если предикат нет. В решении JJ предикаты являются дорогостоящими, что еще больше ухудшает ситуацию. И в довершение всего, издержки в худшем случае при использовании множественная отправка составляют sum of all предложения where, используемые в все multi с.

  • В коде where .^api() == 1 && .^name eq "WithApi" 42 из 43 символов дублируются для каждого варианта multi. Напротив, ограничение типа не-1038 * гораздо короче и не скрывает разницу. Конечно, JJ может объявить subset s схожим эффектом, но тогда это устранит единственную силу их решения, не исправляя его наиболее существенную слабость.

Добавление времени компиляции метаданные; использование его в многократной рассылке

Прежде чем перейти к проблеме JJ, в частности, вот пара вариантов общей техники:

role Fruit {}                             # Declare metadata `Fruit`

my $vegetable-A = 'cabbage';
my $vegetable-B = 'tomato' does Fruit;    # Attach metadata to a value

multi pick (Fruit $produce) { $produce }  # Dispatch based on metadata

say pick $vegetable-B;                    # tomato

То же самое, но параметризовано:

enum Field < Math English > ;

role Teacher[Field] {}                    # Declare parameterizable metadata `Teacher`

my $Ms-England  = 'Ms England'; 
my $Mr-Matthews = 'Mr Matthews';

$Ms-England  does Teacher[Math];
$Mr-Matthews does Teacher[English];

multi field (Teacher[Math])    { Math }
multi field (Teacher[English]) { English }

say field $Mr-Matthews;                   # English

Я использовал role в качестве метаданных, но это случайно. Цель заключалась в том, чтобы иметь метаданные, которые можно присоединить во время компиляции и которые имеют имя типа, чтобы кандидаты для разрешения диспетчеризации могли быть установлены во время компиляции.

Версия метаданных времени компиляции JJ во время выполнения ответ

Решение состоит в том, чтобы объявить метаданные и при необходимости присоединить их к классам JJ.

Вариант решения Брэда:

class WithApi1 {}
class WithApi2 {}

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

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

constant three = anon class WithApi:ver<0.0.2>:api<1> is WithApi1 {} 

multi sub get-api( WithApi1 $foo ) { "That's api 1" }

multi sub get-api( WithApi2 $foo ) { "That's api deuce" }

say get-api(one.new); # That's api 1
say get-api(two.new); # That's api deuce
say get-api(three.new); # That's api 1

Альтернативой является написание одного Параметризуемый элемент метаданных:

role Api[Version $] {}

constant one = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> does Api[v1] {}

constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> does Api[v2] {}

constant three = anon class WithApi:ver<0.0.2>:api<v1> does Api[v1] {} 

multi sub get-api( Api[v1] $foo ) { "That's api 1" }

multi sub get-api( Api[v2] $foo ) { "That's api deuce" }

say get-api(one.new); # That's api 1
say get-api(two.new); # That's api deuce
say get-api(three.new); # That's api 1

Соответствующие диапазоны версий

В комментарии ниже JJ пишет:

Если вы используете where предложения, вы можете иметь multi s, которые отправляют версии до числа (поэтому не нужно создавать по одной для каждой версии)

Решение role, описанное в этом ответе, также может отправлять диапазоны версий, добавив другую роль :

role Api[Range $ where { .min & .max ~~ Version }] {}

...

multi sub get-api( Api[v1..v3] $foo ) { "That's api 1 thru 3" }

#multi sub get-api( Api[v2] $foo ) { "That's api deuce" }

Отображает That's api 1 thru 3 для всех трех вызовов. Если второй мультиэкранный комментарий не закомментирован, он имеет приоритет для вызовов v2.

Обратите внимание, что обычная диспетчеризация get-api все еще проверяется, и кандидат разрешается во время компиляции, несмотря на то, что сигнатура роли включает where пункт. Это связано с тем, что время выполнения предложения роли where выполняется во время компиляции подпрограммы get-api; когда get-api подпрограмма называется условие роли where больше не актуально.

Сноски

1 В Несколько Ограничения , Ларри писал:

Для 6.0.0 ... любая информация о типе структуры, выводимая из предложения where, будет игнорироваться [во время компиляции]

Но на будущее он предположил:

my enum Day ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];

Int $n where 1 <= * <= 5    # Int plus dynamic where
Day $n where 1 <= * <= 5    # 1..5

Первый where считается динамическим c не из-за характера сравнений, а потому, что Int не является конечно-перечислимым , [Второе ограничение] ... может вычислить членство в наборе во время компиляции, потому что оно основано на перечислении Day, и, следовательно, [ограничение, включающее в себя предложение where] считается stati c, несмотря на использование а where.

6 голосов
/ 07 апреля 2020

Решение действительно простое: также псевдоним версии "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

И это также позволяет избавиться от предложения where в подписях.

Имейте в виду, вы не сможете отличить guish их по имени:

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");

Тогда вы сможете различить guish по имени:

say one.^name;  # one
say two.^name;  # two
4 голосов
/ 08 апреля 2020

Только одна вещь может быть в данном пространстве имен.

Я предполагаю, что единственная причина, по которой вы помещаете второе объявление в константу и объявляете его с помощью my, состоит в том, что оно выдает ошибку повторного объявления.

Дело в том, что он должен все же выдавать вам ошибку повторного объявления. Ваш код даже не должен компилироваться.

Вы должны объявить второй с 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> {}

Тогда будет очевидно, почему то, что вы пытаетесь сделать, не делает не работает Во-первых, второе объявление никогда не устанавливается в пространство имен. Поэтому, когда вы используете его во втором подпрограмме multi, он объявляет, что его аргумент того же типа, что и первый класс.

(Даже когда вы используете my в своем коде, он не справляется установить его в пространство имен.)


Вы предполагаете, что пространство имен является плоским пространством имен.
Это не так.

У вас может быть класс с одним имя, но доступно только под другим.

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 {}
#                                                                   ^________^

Затем, когда второй multi проверяет, что его аргумент относится к первому типу, он все равно совпадает, когда вы даете ему второй.

Это не здорово.


На самом деле нет встроенного способа делать именно то, что вы хотите.

Вы можете попытаться создать новый метатип, который может создать новый тип, который будет действовать как классы.

Лично я бы просто присвоил им обоим независимые имена.

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
}
0 голосов
/ 07 апреля 2020

Элизабет Маттийсен ответьте над игрой мне подсказку . Подписи соответствуют символу, а не имени символа. Однако, когда вы используете псевдоним (используя константу) для нового имени, вы все равно сохраняете это имя. Давайте использовать это для создания единого множественного вызова, в котором меняется только версия API:

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

Снова после ответа Элизабет в на предыдущий вопрос , константы используются для нового версии, чтобы избежать столкновений пространства имен, но multis будет выбираться исключительно на основе API-версии относительно безопасным для типов способом, без необходимости использовать псевдонимы символов в подписи. Даже если вы придумаете новую константу для псевдонима WithApi с какими-либо метаданными, мульти будет по-прежнему выбираться на основе версии API (что я и искал).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...