Raku Rebless и несколько классов - PullRequest
6 голосов
/ 04 февраля 2020

(Это продолжение до: Raku rebless больше не работает с унаследованными классами )

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

Идея - это класс Person с подклассами mixin для Child и Adult. У нас есть объект Child, и мы изменяем тип на Adult, когда возраст проходит 18 лет.

Этот объект явно не срабатывает, поскольку Adult - это миксин для Parent, а не для Child:

class Person
{
  has Int $.age is rw = 0;

  method happy-birthday
  {
    $.age++;
    # Metamodel::Primitives.rebless($, Adult) if $.age == 18;
  }

  method can-vote
  {
    ...;
  }
}

constant Adult = Person but role { method can-vote { True  } }

constant Child = Person but role
{
  method can-vote { False }
  method happy-birthday
  {
    $.age++;
    Metamodel::Primitives.rebless(self, Adult) if $.age == 18;
  }

}

BEGIN Child.^set_name('Child');
BEGIN Adult.^set_name('Adult');

my $tom   = Child.new;

say "Age  Can-Vote  Class";

for ^20
{
  say "{ $tom.age.fmt('%3d') }   { $tom.can-vote }    { $tom.^name }";
  $tom.happy-birthday;
}

Но он работает частично:

Age  Can-Vote  Class
  0   False    Child
  1   False    Child
  2   False    Child
  3   False    Child
  4   False    Child
  5   False    Child
  6   False    Child
  7   False    Child
  8   False    Child
  9   False    Child
 10   False    Child
 11   False    Child
 12   False    Child
 13   False    Child
 14   False    Child
 15   False    Child
 16   False    Child
 17   False    Child
Incompatible MROs in P6opaque rebless for types Child and Adult
  in method happy-birthday at ./vote-error line 28

Настройка только с одним классом и одним миксином - вот что:

class Child
{
  has Int $.age is rw = 0;

  method happy-birthday
  {
    $.age++;
    Metamodel::Primitives.rebless($, Adult) if $.age == 18;
  }

  method can-vote
  {
    False;
  }
}

constant Adult = Child but role { method can-vote { True } }

BEGIN Adult.^set_name('Adult');

my $tom = Child.new;

say "Age  Can-Vote  Class";

for ^20
{
  say "{ $tom.age.fmt('%3d') }   { $tom.can-vote }    { $tom.^name }";
  $tom.happy-birthday;
}

За исключением того, что он не работает:

 Error while compiling vote-error1
Illegally post-declared type:
    Adult used at line 10

Я понял. Линия rebless относится к Adult, который еще не был объявлен. Итак, я попытался заглушить класс:

class Child { ... }

constant Adult = Child but role { method can-vote { True } }

class Child
{
  has Int $.age is rw = 0;

  method happy-birthday
  {
    $.age++;
    Metamodel::Primitives.rebless($, Adult) if $.age == 18;
  }

  method can-vote
  {
    False;
  }
}

BEGIN Adult.^set_name('Adult');

my $tom = Child.new;

say "Age  Can-Vote  Class";

for ^20
{
  say "{ $tom.age.fmt('%3d') }   { $tom.can-vote }    { $tom.^name }";
  $tom.happy-birthday;
}

Но заглушки и наследование не любят друг друга:

===SORRY!=== Error while compiling vote-error2
'Child+{<anon|1>}' cannot inherit from 'Child' because 'Child' isn't composed yet (maybe it is stubbed)

Затем я попытался добавить новый миксин, чтобы избежать проблемы циклических ссылок:

class Child
{
  has Int $.age is rw = 0;

  method can-vote
  {
    False;
  }
}

constant Adult = Child but role { method can-vote { True } }
BEGIN Adult.^set_name('Adult');

role still-a-child
{
  method happy-birthday
  {
    $.age++;
    Metamodel::Primitives.rebless($, Adult) if $.age == 18;
  }
}

my $tom = Child.new but still-a-child;

say "Age  Can-Vote  Class";

for ^20
{
  say "{ $tom.age.fmt('%3d') }   { $tom.can-vote }    { $tom.^name }";
  $tom.happy-birthday;
}

Но это тоже не удалось:

Age  Can-Vote  Class
  0   False    Child+{still-a-child}
  1   False    Child+{still-a-child}
  2   False    Child+{still-a-child}
  3   False    Child+{still-a-child}
  4   False    Child+{still-a-child}
  5   False    Child+{still-a-child}
  6   False    Child+{still-a-child}
  7   False    Child+{still-a-child}
  8   False    Child+{still-a-child}
  9   False    Child+{still-a-child}
 10   False    Child+{still-a-child}
 11   False    Child+{still-a-child}
 12   False    Child+{still-a-child}
 13   False    Child+{still-a-child}
 14   False    Child+{still-a-child}
 15   False    Child+{still-a-child}
 16   False    Child+{still-a-child}
 17   False    Child+{still-a-child}
Cannot change the type of a Any type object
  in method happy-birthday at vote-error3 line 26

И это произошло, потому что $ tom теперь нечто иное, чем Child, и Adult не является смесью того, что мы имеем сейчас , Но сообщение об ошибке не очень полезно.

Последнее по сути то же самое, что и первое.

И я застрял.

Ответы [ 2 ]

6 голосов
/ 04 февраля 2020

TL; DR Я опишу несколько вопросов. В конце я покажу решение, которое компилируется и работает на недавнем (2020) Rakudo. Это простой вариант вашего собственного кода, но я не достаточно осведомлен, чтобы ручаться за его правильность, не говоря уже о целесообразности [1] [2] .

Cannot change the type of a Any type object

Сообщение об ошибке поступает из строки rebless:

Metamodel::Primitives.rebless($, Adult) if $.age == 18;

A $ как термин [3] не означает self, но вместо этого анонимное состояние Scalar переменная . По умолчанию он содержит Any, отсюда и сообщение об ошибке. Это должно быть self. [4]

Исправив эту первую проблему, мы получаем новую, в зависимости от используемой версии Rakudo:

  • Старые Rakudo: Incompatible MROs in P6opaque rebless for types Child and Adult.

  • Более новые Rakudo: New type Adult for Child is not a mixin type.

Как и первое сообщение об ошибке, которое мы только что исправили, эти два также запускаются оператором rebless. [5]

Мы должны решить обе проблемы.

В более новом Rakudo, исправления проблем Cannot change the type of a Any type object и not a mixin type недостаточно, если я использую ваш код "добавления нового миксина"; Я просто получаю ошибку Incompatible MROs.

И наоборот, использование альтернативного кода, исправляющего проблему Incompatible MROs на старом Rakudo, приводит к not a mixin type, если , что проблема не решена должным образом. (В моей первоначальной версии этого ответа я решил проблему Incompatible MROs, а затем пренебрег тестом на более новом Rakudo!)

Ваш диагноз ошибки Incompatible MROs был "Этот явно не проходит, так как Adult - это миксин на Person, а не на Child ". Я прочитал это, взглянул на код, поверил тебе и пошел дальше. Но потом я вернулся к той же проблеме, используя код, который вы написали, чтобы попытаться решить ее. Что дает?

Исходя из моих экспериментов, кажется, что не только класс "to" (класс которого должен быть новым классом объекта, подлежащего анализу) должен иметь MRO, который совместим с объектом, который подвергается анализу. в соответствии с вещами, которые я ожидал (например, наследование классов), но также объект "from" (подлежащий обработке) не может быть оба :

  • На основе класса, который имеет атрибуты.

  • Уже включены в.

(я не знаю, является ли это ошибкой, которая может быть исправлено или неизбежным ограничением. Я знаю, что недавнее (2020) Rakudo имеет это ограничение, используя оба варианта кода, предложенного Джонатаном в предыдущем SO.)

Это означает, что «добавление нового миксина, чтобы избежать проблема циклической ссылки "(" заглушки и наследование не нравятся друг другу ") не решает вашу проблему.

Вместо этого я вернулся к вашей попытке" всего один класс и один миксин "(которая закончилась с Illegally post-declared type в течение м, вы изначально написали это) и попробовали другой подход, чтобы обойти эту ошибку.

Следующий вариант вашего кода "просто один класс и один миксин" работает на Rakudo v2020.01.114. gcfe.2.cd c .56. Все, что я сделал, превратил константу Adult в переменную. Я написал ... для остального кода, который совпадает с вашим кодом:

my $Adult;

...
    Metamodel::Primitives.rebless(self, $Adult) if $.age == 18;
...

$Adult = Child but role { method can-vote { True } }
$Adult.^set_name('Adult');

...

Hth.

Сноски

[1 ] Решение Джонатана в недавнем SO использовало конструкции времени компиляции для Adult. Мое решение следует примеру Джонатана, за исключением того, что оно строит мишень для рефлекса $Adult в время выполнения . Я не уверен, что это технически безопасно перед лицом новой оптимизации, представленной @JonathanWorthington. Я постараюсь «призвать» его прокомментировать.

[2] Кроме этой сноски, мой ответ не касается мудрости использования rebless. Два вопроса сразу приходят мне на ум. Во-первых, это надежная функциональность с учетом турофилии , которая, несомненно, является центральной для вас, даже если вам нужно спросить ваших последних SO. (И с этим, metaturophilia. То есть, у нас есть дыры в нашем подходе к созреванию Raku, языка, и Rakudo, реализации. Поскольку код степени, написанный одним из нас, приводит к заполнению пробелов, мы все можем быть благодарны.) Во-вторых, это надежная документация СС, учитывая, что (насколько я могу судить) некоторая ключевая документация нарушает общее правило ограничивать себя спецификацией Raku согласно roast и вместо этого "в значительной степени отражает систему метаобъектов, реализованную компилятором Rakudo Raku" . Я просто исправляю ошибки до тех пор, пока ваш код не скомпилируется и не запустится в версии Rakudo 2020 года.

[3] См. Что такое термин? в связи с некоторый контекст в этом комментарии .

[4] Некоторые люди могут предположить, что если $.foo является .foo из self, то $ должен быть self. Такое мышление было бы разумной презумпцией, если бы у raku был типичный контекстно-свободный токенизация, используемый для большинства языков программирования. Более того, оно , как правило, относится и к коду Raku, так же, как это обычно применяется даже на естественном языке. (Если за маркером Engli sh «my» следует «self», то это, вероятно, означает то же самое, что и «я».) Но грамматика Raku сознательно сочетает чувствительность к контексту , синтаксический анализ без сканирования и maxim munch для поддержки создания языков с более естественным чувством, чем это характерно для языков программирования. И здесь мы видим пример. В «термине положение» [3] вход $.foo распознается как один токен вместо двух ($, за которым следует .foo), тогда как вход $,... распознается как два токена ( $, за которым следует оператор разделителя списка ,), а не один.

[5] Все эти сообщения об ошибках генерируются в частях Rakudo, которые находятся близко к металлу. Если вы используете MoarVM в качестве бэкэнда, они приходят из файла P6opaque. c.

1 голос
/ 09 февраля 2020

Спасибо. Но я не могу заставить его работать:

class Adult { ... }

class Child
{
  has Int $.age is rw = 0;

  method happy-birthday
  {
    $.age++;
    Metamodel::Primitives.rebless(self, Adult) if $.age == 18;
  }

  method can-vote
  {
    False;
  }
}

role grown-up { method can-vote { True } }

class Adult is Child does grown-up { }

my $tom = Child.new;

say "Age  Can-Vote  Class";

for ^20
{
  say "{ $tom.age.fmt('%3d') }   { $tom.can-vote }    { $tom.^name }";
  $tom.happy-birthday;
}

Это работает до 17, а затем завершается с

New type Adult for Child is not a mixin type

Чего мне не хватает?

(И Stackowerflow должен разрешить код в комментариях.)

...