<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Удачный Raku'rs</title><subtitle>Русскоязычный блог о программировании на языке Raku. Тонкости, элегантные решения и повседневные задачи.</subtitle><author><name>Удачный Raku'rs</name></author><id>https://teletype.in/atom/goodrakurs</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/goodrakurs?offset=0"></link><link rel="alternate" type="text/html" href="https://rakurs.atroxaper.net/?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=goodrakurs"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/goodrakurs?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-21T04:35:43.766Z</updated><entry><id>goodrakurs:2021-12-05-java-annotations</id><link rel="alternate" type="text/html" href="https://rakurs.atroxaper.net/2021-12-05-java-annotations?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=goodrakurs"></link><title>Java аннотации в Raku | Java Annotations in Raku</title><published>2021-12-05T15:21:36.382Z</published><updated>2021-12-10T09:29:27.829Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/da/f2/daf2260d-90f7-4db6-bfe2-b85e475c2437.jpeg"></media:thumbnail><tt:hashtag>rakulang</tt:hashtag><tt:hashtag>traits</tt:hashtag><tt:hashtag>java</tt:hashtag><tt:hashtag>annotation</tt:hashtag><tt:hashtag>аннотации</tt:hashtag><tt:hashtag>deprecated</tt:hashtag><tt:hashtag>override</tt:hashtag><tt:hashtag>suppresswarnings</tt:hashtag><tt:hashtag>serialization</tt:hashtag><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/54/8a/548a6ab2-5658-47d5-b6f3-455fc0583e2e.jpeg&quot;&gt;Сегодня немного про то, что новое лучше усваивается через уже известное. Так сложилось, что на $dayjob я пишу на Java, по-этому зайду именно с его стороны. В Java 1.5 появилась интересная синтаксическая форма — аннотации. Выглядит это как-то так:</summary><content type="html">
  &lt;figure id=&quot;MsUh&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/54/8a/548a6ab2-5658-47d5-b6f3-455fc0583e2e.jpeg&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VFhp&quot;&gt;Сегодня немного про то, что новое лучше усваивается через уже известное. Так сложилось, что на $dayjob я пишу на Java, по-этому зайду именно с его стороны. В Java 1.5 появилась интересная синтаксическая форма — &lt;a href=&quot;https://docs.oracle.com/javase/tutorial/java/annotations/&quot; target=&quot;_blank&quot;&gt;аннотации&lt;/a&gt;. Выглядит это как-то так:&lt;/p&gt;
  &lt;pre id=&quot;X9kX&quot; data-lang=&quot;java&quot;&gt;/**
 * @deprecated use #getId() method instead
 */
@Override
public String getName() {
  return &amp;quot;stub&amp;quot;
}&lt;/pre&gt;
  &lt;p id=&quot;DXqe&quot;&gt;В примере показана аннотация &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Deprecated.html&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;@Deprecated&lt;/code&gt;&lt;/a&gt;, которая заставляет среду выполнения выводить предупреждение в консоль каждый раз, когда используется метод &lt;code&gt;getName&lt;/code&gt;. Кроме того, в Javadoc добавлена поясняющая информация.&lt;/p&gt;
  &lt;p id=&quot;MAAu&quot;&gt;Вообще, аннотации в Java это механизм добавления некоторых метаданных в классы, объекты, типы и прочее, которые можно использовать в последствии на этапе компиляции, исполнения или статического анализа кода. С помощью них, например, можно реализовать стратегию разделения (decouple) кода — чтобы одни компоненты программы работали совместно с другими, не имея жёсткой связи. На этой стратегии построена идея &lt;a href=&quot;https://en.wikipedia.org/wiki/Inversion_of_control&quot; target=&quot;_blank&quot;&gt;Инверсии Управления&lt;/a&gt; и ядро библиотеки &lt;a href=&quot;https://spring.io&quot; target=&quot;_blank&quot;&gt;Spring&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;3Uxp&quot;&gt;Но хватит про Java. Что есть в Raku похожего на механизм аннотаций? В Raku есть &lt;a href=&quot;https://docs.raku.org/language/traits&quot; target=&quot;_blank&quot;&gt;Traits&lt;/a&gt; — синтаксис, которым можно пометить классы и объекты. Эти метки обрабатываются во время компиляции программы. В зависимости от желания программиста, эффект такой обработки может оказывать влияние и на ход выполнения программы.&lt;/p&gt;
  &lt;p id=&quot;vkEb&quot;&gt;Например, рассмотрим аналогичную аннотации &lt;code&gt;@Deprecated&lt;/code&gt; конструкцию из спецификации Raku:&lt;/p&gt;
  &lt;pre id=&quot;GMBN&quot; data-lang=&quot;perl&quot;&gt;sub get-name(--&amp;gt; Str) is DEPRECATED(&amp;#x27;get-id() method&amp;#x27;) {
  &amp;#x27;stub&amp;#x27;
}&lt;/pre&gt;
  &lt;p id=&quot;KfxO&quot;&gt;&lt;code&gt;is DEPRECATED&lt;/code&gt; и есть trait. В аргументе можно сообщить альтернативу устаревшему коду. После завершения программы, во время выполнения которой вызывалась функция &lt;code&gt;get-name()&lt;/code&gt;, будет выведено сообщение о том, где и сколько раз выполнялся устаревший код:&lt;/p&gt;
  &lt;pre id=&quot;XcMF&quot;&gt;Saw 1 occurrence of deprecated code.
======================================================================
Sub get-name (from GLOBAL) seen at:
  ~/advent.raku, line 13
Please use get-id() method instead.
----------------------------------------------------------------------
Please contact the author to have these occurrences of deprecated code
adapted, so that this message will disappear!&lt;/pre&gt;
  &lt;p id=&quot;AiSm&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;3unc&quot;&gt;Устареваем&lt;/h3&gt;
  &lt;p id=&quot;eWl7&quot;&gt;&lt;code&gt;is DEPRECATED&lt;/code&gt; это trait из стандартной библиотеки. Чтобы разобраться в том, как он работает, попробуем написать свой аналог под названием &lt;code&gt;obsolete&lt;/code&gt;. Сначала определимся с хранилищем собираемой информации — класс, который хранит и обновляет количество вызовов функции и умеет вывести отчёт:&lt;/p&gt;
  &lt;pre id=&quot;OKFF&quot; data-lang=&quot;perl&quot;&gt;class ObsoletTraitData {
  has $.routine-name is required;
  has $.user-hint;
  has $!execution-amount = 0;
  method executed() { $!execution-amount++ }
  method report() {
    return unless $!execution-amount;
    note &amp;quot;Obsolete routine $!routine-name is execcuted $!execution-amount times.&amp;quot;;
    note $_ with $!user-hint;
  }
}&lt;/pre&gt;
  &lt;p id=&quot;HhTt&quot;&gt;Теперь объявляем тестовый trait — это обычная &lt;code&gt;multi&lt;/code&gt; функция с именем &lt;code&gt;trait_mod:&amp;lt;is&amp;gt;&lt;/code&gt; и двумя аргументами: первый — к чему будет применяться trait (в нашем случае это функция &lt;code&gt;Routine&lt;/code&gt;), второй — имя:&lt;/p&gt;
  &lt;pre id=&quot;2iV8&quot; data-lang=&quot;perl&quot;&gt;say &amp;#x27;run-time&amp;#x27;;
multi trait_mod:&amp;lt;is&amp;gt;(Routine $r, :$obsolete!) {
  say &amp;#x27;compile-time&amp;#x27;
}
sub get-name(--&amp;gt; Str) is obsolete {
  &amp;#x27;stub&amp;#x27;
}
say get-name;&lt;/pre&gt;
  &lt;pre id=&quot;vAuD&quot;&gt;# Output: compile-time
#         run-time 
#         stub&lt;/pre&gt;
  &lt;p id=&quot;KUWM&quot;&gt;Самое важное, что нужно понять о traits — их функции исполняются во время компиляции, а не выполнения программы. Это наглядно видно из вывода кода выше. Вспомним, чего мы хотим добиться — отчета об исполнении устаревшего кода &lt;em&gt;перед завершением&lt;/em&gt; работы программы. Эту информацию мы можем получить только во время исполнения. Чтобы повлиять на исполнение из стадии компиляции, trait должен как-то модифицировать функцию. В нашем случае можно добавить в функцию &lt;a href=&quot;https://docs.raku.org/language/phasers&quot; target=&quot;_blank&quot;&gt;phaser&lt;/a&gt; &lt;code&gt;&lt;a href=&quot;https://docs.raku.org/language/phasers#ENTER&quot; target=&quot;_blank&quot;&gt;ENTER&lt;/a&gt;&lt;/code&gt;. Это специальный блок, который выполняется перед выполнением первой инструкции функции. То есть, мы ходим, чтобы функция &lt;code&gt;get-name&lt;/code&gt; выглядела как-то так:&lt;/p&gt;
  &lt;pre id=&quot;vx3x&quot; data-lang=&quot;perl&quot;&gt;sub get-name(--&amp;gt; Str) {
  ENTER { $obsolet-trait-data.executed }
  &amp;#x27;stub&amp;#x27;
}&lt;/pre&gt;
  &lt;p id=&quot;rO1G&quot;&gt;Код самой функции мы трогать не можем, но можем сделать необходимые манипуляции во время компиляции. Берём имя функции, возможную подсказку для пользователя, заводим новый объект типа &lt;code&gt;ObsoletTraitData&lt;/code&gt;, кладём его в локальную ассоциативную переменную &lt;code&gt;%obsolet-trait-data&lt;/code&gt; и добавляем необходимый phaser:&lt;/p&gt;
  &lt;pre id=&quot;g86J&quot; data-lang=&quot;perl&quot;&gt;my ObsoletTraitData %obsolet-trait-data;

multi trait_mod:&amp;lt;is&amp;gt;(Routine $r, :$obsolete!) {
  my $routine-name = $r.name;
  my $user-hint = $obsolete ~~ Str ?? $obsolete !! Any;
  %obsolet-trait-data{$routine-name} =
    ObsoletTraitData.new(:$routine-name, :$user-hint);
  $r.add_phaser(&amp;#x27;ENTER&amp;#x27;, -&amp;gt; {
    %obsolet-trait-data{$routine-name}.executed;
  });
}&lt;/pre&gt;
  &lt;p id=&quot;ZPY2&quot;&gt;Теперь, при выполнении функции &lt;code&gt;get-name&lt;/code&gt;, объект &lt;code&gt;ObsoletTraitData&lt;/code&gt; будет обновлять своё состояние. Таким образом, мы повлияли на ход выполнения программы во время её компиляции. Осталось только вывести отчёт. Для этого мы добавим ещё один phaser &lt;code&gt;&lt;a href=&quot;https://docs.raku.org/language/phasers#END&quot; target=&quot;_blank&quot;&gt;END&lt;/a&gt;&lt;/code&gt; в основной код. Его блок исполняется перед самым завершением программы. Таким образом получим следующую картину:&lt;/p&gt;
  &lt;pre id=&quot;36nE&quot; data-lang=&quot;perl&quot;&gt;class ObsoletTraitData { ... }

my ObsoletTraitData %obsolet-trait-data;

END { .report for %obsolet-trait-data.values }

multi trait_mod:&amp;lt;is&amp;gt;(Routine $r, :$obsolete!) { ... }

sub get-name(--&amp;gt; Str) is obsolete(&amp;#x27;Please use get-id() instead.&amp;#x27;) {
  &amp;#x27;stub&amp;#x27;
}
sub anouther-obsolet() is obsolete {}

get-name();
anouther-obsolet();
get-name();&lt;/pre&gt;
  &lt;pre id=&quot;E47w&quot;&gt;# Output:
# Obsolete routine get-name is execcuted 2 times.
# Please use get-id() instead.
# Obsolete routine anouther-obsolet is execcuted 1 times.&lt;/pre&gt;
  &lt;p id=&quot;mzdV&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;L8No&quot;&gt;Переопределяем&lt;/h3&gt;
  &lt;p id=&quot;nVmf&quot;&gt;С &lt;code&gt;@Deprecated&lt;/code&gt; закончили. Другая часто используемая аннотация в Java это &lt;code&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Override.html&quot; target=&quot;_blank&quot;&gt;@Override&lt;/a&gt;&lt;/code&gt; - ей помечется метод класса. Случай, когда он не переопределяет метод супер-класса, считается ошибкой компиляции. Сделать аналогичный trait будет несложно - нам не придёт выходить за пределы стадии компиляции. Объявляем trait с именем &lt;code&gt;override&lt;/code&gt;, применимый только к методам:&lt;/p&gt;
  &lt;pre id=&quot;gZiy&quot; data-lang=&quot;perl&quot;&gt;multi trait_mod:&amp;lt;is&amp;gt;(Method $m, :$override!) {&lt;/pre&gt;
  &lt;p id=&quot;FySi&quot;&gt;Проверяем, что метод является членом класса, иначе заканчиваем работу:&lt;/p&gt;
  &lt;pre id=&quot;XFFn&quot; data-lang=&quot;perl&quot;&gt;return unless $m.package.HOW ~~ Metamodel::ClassHOW;&lt;/pre&gt;
  &lt;p id=&quot;BL4H&quot;&gt;Проверяем, что у класса обладателя метода есть родители. Для этого воспользуемся мета-методом &lt;code&gt;&lt;a href=&quot;https://docs.raku.org/routine/mro&quot; target=&quot;_blank&quot;&gt;^mro&lt;/a&gt;&lt;/code&gt;, который отдаст список всех родительских классов, включая его самого, &lt;code&gt;Any&lt;/code&gt; и &lt;code&gt;Mu&lt;/code&gt; (их мы из рассмотрения отфильтровываем):&lt;/p&gt;
  &lt;pre id=&quot;1ZrR&quot; data-lang=&quot;perl&quot;&gt;my $class = $m.package;
my $method-point = $class.^name ~ &amp;#x27;::&amp;#x27; ~ $m.name;
my @parents = $class.^mro[1 ..^ *-2];
die &amp;quot;is override trait cannot be used without parent class $method-point.&amp;quot; unless @parents;&lt;/pre&gt;
  &lt;p id=&quot;xE3b&quot;&gt;Проходим по всем родителям и их методам в поисках одного, который совпадает по имени и &lt;a href=&quot;https://docs.raku.org/type/Signature&quot; target=&quot;_blank&quot;&gt;сигнатуре&lt;/a&gt;. Сравнения сигнатур методов на одинаковость это не очень тривиальная задача, и здесь мы скроем её реализацию за функцией&lt;code&gt;check-signature-eq&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;tfOL&quot; data-lang=&quot;perl&quot;&gt;for @parents -&amp;gt; $parent {
  for $parent.^methods -&amp;gt; $parent-method {
    return if $parent-method.name eq $m.name &amp;amp;&amp;amp;
      check-signature-eq($parent-method.signature, $m.signature)
  }
}&lt;/pre&gt;
  &lt;p id=&quot;KOFs&quot;&gt;В случае, если искомого метода у родителей не нашлось, придётся выдавать ошибку:&lt;/p&gt;
  &lt;pre id=&quot;4Zhq&quot; data-lang=&quot;perl&quot;&gt;die &amp;quot;$method-point does not override any parent methods.&amp;quot;;&lt;/pre&gt;
  &lt;p id=&quot;R88y&quot;&gt;В результате получаем следующее:&lt;/p&gt;
  &lt;pre id=&quot;grBO&quot; data-lang=&quot;perl&quot;&gt;multi trait_mod:&amp;lt;is&amp;gt;(Method $m, :$override!) { ... }

class A {
  method from-a(:$r) {}
}

class B is A {
  method from-a($r) is override {
    say &amp;#x27;from-b&amp;#x27;
  }
}&lt;/pre&gt;
  &lt;pre id=&quot;7Yl0&quot;&gt;# Output: B::from-a does not override any parent methods.
# Exit code: 1&lt;/pre&gt;
  &lt;p id=&quot;qNfP&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Trwz&quot;&gt;Подавляем&lt;/h3&gt;
  &lt;p id=&quot;vv9T&quot;&gt;Нам уже удалось реализовать логику работы Java аннотаций &lt;code&gt;@Deprecated&lt;/code&gt; и &lt;code&gt;@Override&lt;/code&gt;. Попробуем реализовать логику &lt;code&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/SuppressWarnings.html&quot; target=&quot;_blank&quot;&gt;@SuppressWarnings&lt;/a&gt;&lt;/code&gt;. Эта аннотация применяется к функции и подавляет её предупредительные сообщения. Так же, можно указать какие именно предупреждения будут подавляться.&lt;/p&gt;
  &lt;p id=&quot;deTQ&quot;&gt;В Raku предупреждения можно вывести с помощью функции &lt;code&gt;&lt;a href=&quot;https://docs.raku.org/routine/warn&quot; target=&quot;_blank&quot;&gt;warn&lt;/a&gt;&lt;/code&gt;. Она порождает специальное исключение, которое выводится в поток ошибок, а процесс выполнения возобновляется с прежнего места. Перехватить такое исключение можно с помощью специального phaser &lt;code&gt;&lt;a href=&quot;https://docs.raku.org/language/phasers#CONTROL&quot; target=&quot;_blank&quot;&gt;CONTROL&lt;/a&gt;&lt;/code&gt;. То есть, как и в случае с &lt;code&gt;@Deprecated&lt;/code&gt;, нам нужно модифицировать функцию, добавив нужный phaser. Давайте попробуем что-то новое и вместо &lt;code&gt;add_phaser&lt;/code&gt; используем &lt;a href=&quot;https://docs.raku.org/language/functions#index-entry-dispatch_wrapped_routines&quot; target=&quot;_blank&quot;&gt;обёртку&lt;/a&gt; функции. Как это работает? Мы заменяем одну функцию другой, которая может по своему усмотрению вызвать оригинал (методом &lt;code&gt;&lt;a href=&quot;https://docs.raku.org/language/functions#index-entry-dispatch_callsame&quot; target=&quot;_blank&quot;&gt;callsame&lt;/a&gt;&lt;/code&gt;). Внутрь этой функции мы и вставим phaser &lt;code&gt;CONTROL&lt;/code&gt;, который будет имитировать стандартное поведение, но только не для подавляемых предупреждений:&lt;/p&gt;
  &lt;pre id=&quot;c4ER&quot; data-lang=&quot;perl&quot;&gt;multi trait_mod:&amp;lt;is&amp;gt;(Routine $b, :$suppress-warnings) {
  my $regex = $suppress-warnings ~~ Str ?? / &amp;lt;$suppress-warnings&amp;gt; / !! Any;
  $b.wrap(sub with-control(|c) {
    callsame;
    CONTROL {
      when CX::Warn {
        .note if $regex.defined &amp;amp;&amp;amp; $_.message !~~ $regex;
        .resume
      }
    }
  });
}

sub work-in-progress() is suppress-warnings(&amp;#x27;todo&amp;#x27;) {
  warn &amp;#x27;important warn&amp;#x27;;
  warn &amp;#x27;todo warn&amp;#x27;;
  say &amp;#x27;WIP&amp;#x27;;
}

work-in-progress()&lt;/pre&gt;
  &lt;pre id=&quot;t33k&quot;&gt;# Output:
# important warn
#  in sub work-in-progress at ~/trait-supress.raku line 15
# WIP&lt;/pre&gt;
  &lt;h3 id=&quot;apkg&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;Cvi8&quot;&gt;Сериализуем&lt;/h3&gt;
  &lt;p id=&quot;dzDl&quot;&gt;Осталось только обсудить аннотации, определяемые пользователем. Как я уже сказал выше, аннотации Java это способ прикрепить некоторую мета-информацию к классу или объекту. После этого, во время компиляции или чаще всего исполнения, аннотированные объекты проверяются на предмет обладания нужной информацией. В Raku для этого отлично подойдут &lt;a href=&quot;https://docs.raku.org/language/objects#index-entry-declarator_role-Roles&quot; target=&quot;_blank&quot;&gt;роли&lt;/a&gt;. Рассмотрим задачу добавления в класс простейшей системы сериализации. Напишем класс и разметим нашим будущим trait:&lt;/p&gt;
  &lt;pre id=&quot;m6V9&quot; data-lang=&quot;perl&quot;&gt;class Person is serialize-name(&amp;#x27;Passport&amp;#x27;) {
  has $.first;
  has $.second is serialize-name(&amp;#x27;Second name&amp;#x27;);
  has $.third is serialize-name(&amp;#x27;Honorific&amp;#x27;);
}&lt;/pre&gt;
  &lt;p id=&quot;ULaA&quot;&gt;Видно, что trait &lt;code&gt;serialize-name&lt;/code&gt; применяется и к самому классу и к его атрибутам.&lt;/p&gt;
  &lt;p id=&quot;NIKw&quot;&gt;Trait для атрибута выглядит так:&lt;/p&gt;
  &lt;pre id=&quot;JVgL&quot; data-lang=&quot;perl&quot;&gt;role SerializableAttribute {
  has $.serialize-name;
}

multi trait_mod:&amp;lt;is&amp;gt;(Attribute $a, :$serialize-name) {
  $a does SerializableAttribute(:$serialize-name);
}&lt;/pre&gt;
  &lt;p id=&quot;iEDH&quot;&gt;Тут trait добавляет в атрибут новую роль &lt;code&gt;SerializableAttribute&lt;/code&gt;. Эта роль сама привносит новый атрибут в атрибут :) Значение нового атрибута trait передаёт через свой аргумент.&lt;/p&gt;
  &lt;p id=&quot;FOjV&quot;&gt;Trait для класса выглядит следующим образом:&lt;/p&gt;
  &lt;pre id=&quot;V8Ml&quot; data-lang=&quot;perl&quot;&gt;role SerializableClass[$name] {
  method serialize() {
    say $name, &amp;#x27; | &amp;#x27;, self.^name;
    say .serialize-name, &amp;#x27; &amp;lt;- &amp;#x27;, .get_value(self)
      for self.^attributes(:local).grep(*.^can(&amp;#x27;serialize-name&amp;#x27;));
  }
}

multi trait_mod:&amp;lt;is&amp;gt;(Mu:U $c, :$serialize-name!) {
  return unless $c.HOW ~~ Metamodel::ClassHOW;
  $c.^add_role(SerializableClass[$serialize-name]);
}&lt;/pre&gt;
  &lt;p id=&quot;W7HB&quot;&gt;Тут trait проверяет, что применяется именно к классу и добавляет специальную роль &lt;code&gt;SerializableClass&lt;/code&gt;. Эта роль добавляет в класс новый метод &lt;code&gt;serialize&lt;/code&gt;, который реализует всю логику по сериализации. В частности, он фильтрует список всех атрибутов класса по признаку наличия метода &lt;code&gt;serialize-name&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;eoPT&quot;&gt;Если всё это запустить, то получим:&lt;/p&gt;
  &lt;pre id=&quot;V4Eo&quot; data-lang=&quot;perl&quot;&gt;Person.new(:first&amp;lt;John&amp;gt;, :second&amp;lt;Hancock&amp;gt;, :third&amp;lt;Mr&amp;gt;).serialize();&lt;/pre&gt;
  &lt;pre id=&quot;ZmzS&quot;&gt;# Output:
# Passport | Person
# second-name &amp;lt;- Hancock
# honorific &amp;lt;- Mr&lt;/pre&gt;
  &lt;p id=&quot;CoCw&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;dBkm&quot;&gt;Заключение&lt;/h3&gt;
  &lt;p id=&quot;Gt5Z&quot;&gt;Как мы видим, traits это довольно мощный инструмент, но, как и всё в мире Raku, его можно использовать очень по-разному. Например, в Java, при объявлении своей аннотации, программист должен указать, до какой стадии распространяется её действие (только на уровне кода, до конца компиляции или до завершения приложения). Ещё можно указать, будет ли аннотация наследоваться дочерними классами, и можно ли её указывать несколько раз. С другой стороны, traits в Raku предоставляют программисту полную свободу действий. Теперь вы обладаете знаниями, достаточными, чтобы написать свою IoC/DI систему наподобие Java Spring Core с помощью Raku traits.&lt;/p&gt;
  &lt;blockquote id=&quot;5iXW&quot; data-align=&quot;center&quot;&gt;&lt;a href=&quot;https://raku-advent.blog/2021/12/10/java-annotations-in-raku-or-my-annotation-is-role/&quot; target=&quot;_blank&quot;&gt;English version&lt;/a&gt;&lt;/blockquote&gt;
  &lt;tt-tags id=&quot;s0AK&quot;&gt;
    &lt;tt-tag name=&quot;rakulang&quot;&gt;#rakulang&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;traits&quot;&gt;#traits&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;java&quot;&gt;#java&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;annotation&quot;&gt;#annotation&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;аннотации&quot;&gt;#аннотации&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;deprecated&quot;&gt;#deprecated&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;override&quot;&gt;#override&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;suppresswarnings&quot;&gt;#suppresswarnings&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;serialization&quot;&gt;#serialization&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;

</content></entry><entry><id>goodrakurs:2021-11-30-mirroring-zef-repository</id><link rel="alternate" type="text/html" href="https://rakurs.atroxaper.net/2021-11-30-mirroring-zef-repository?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=goodrakurs"></link><title>Библиотека модулей Raku: Зеркалируем репозитории | Raku Module Library: Mirroring repositories</title><published>2021-11-30T08:19:56.951Z</published><updated>2021-12-05T15:23:11.335Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/8d/b3/8db36180-0b20-4c26-b5da-5e6b5a5471a9.png"></media:thumbnail><tt:hashtag>rakulang</tt:hashtag><tt:hashtag>удачныйракурс</tt:hashtag><tt:hashtag>zef</tt:hashtag><tt:hashtag>fez</tt:hashtag><tt:hashtag>repository</tt:hashtag><tt:hashtag>modules</tt:hashtag><tt:hashtag>docker</tt:hashtag><tt:hashtag>nginx</tt:hashtag><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/7e/f5/7ef5f1c5-2039-4b86-91e0-12aaa97c8f8e.png&quot;&gt;Популярность языка программирования зависит не только (и не столько) от его дизайна и реализации, но и от огромного числа сторонних вещей. Таких, как качество и количество документации, удобная среда разработки, отзывчивое сообщество единомышленников, библиотека модулей на все случаи жизни и прочее. Сегодня как раз про библиотеку модулей языка Raku. А точнее, про систему их хранения и распространения.</summary><content type="html">
  &lt;figure id=&quot;tsrE&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7e/f5/7ef5f1c5-2039-4b86-91e0-12aaa97c8f8e.png&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;drjp&quot;&gt;Популярность языка программирования зависит не только (и не столько) от его дизайна и реализации, но и от огромного числа сторонних вещей. Таких, как качество и количество документации, удобная среда разработки, отзывчивое сообщество единомышленников, библиотека модулей на все случаи жизни и прочее. Сегодня как раз про библиотеку модулей языка &lt;code&gt;Raku&lt;/code&gt;. А точнее, про систему их хранения и распространения.&lt;/p&gt;
  &lt;p id=&quot;KTYg&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;c1MI&quot;&gt;Что есть в природе&lt;/h3&gt;
  &lt;p id=&quot;m8bL&quot;&gt;Если посмотреть в миры Java, Python, JavaScript, Perl — всё примерно одинаково. Есть де-факто центральный публичный репозиторий (Nexus, PyPI, npm-registry, CPAN), есть сколько-то альтернатив и зеркал, есть возможность поднять свой.&lt;/p&gt;
  &lt;p id=&quot;4daS&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;5zlc&quot;&gt;Что есть в мире Raku&lt;/h3&gt;
  &lt;p id=&quot;ETzG&quot;&gt;Основной менеджер пакетов для &lt;code&gt;Raku&lt;/code&gt; называется &lt;code&gt;&lt;a href=&quot;https://github.com/ugexe/zef&quot; target=&quot;_blank&quot;&gt;zef&lt;/a&gt;&lt;/code&gt;. Сейчас он работает следующим образом: берёт с какого-то сервера индекс, анализирует его и скачивает необходимые перечисленные в нём модули. Так можно делать с несколькими серверами.&lt;/p&gt;
  &lt;p id=&quot;tSQg&quot;&gt;Изначально, в качестве сервера для распространения модулей служил &lt;code&gt;GitHub&lt;/code&gt;. Это дешёвое и сердитое решение. Индексы считают на сторонней машине про cron и выкладывают в отдельный git репозиторий. Сейчас в этом ‘сервере’ хранятся индексы о более чем восьмистах версий различных модулей. Эта схема получила название &lt;code&gt;p6с&lt;/code&gt;. Минусы её очевидны — завязывание на одну проприетарную платформу, которая изначально спроектирована для других целей.&lt;/p&gt;
  &lt;p id=&quot;T4NZ&quot;&gt;Второй попыткой стала проба использовать &lt;code&gt;CPAN&lt;/code&gt; для нужд &lt;code&gt;Raku&lt;/code&gt;. Если без подробностей, то модули в &lt;code&gt;Perl&lt;/code&gt; и &lt;code&gt;Raku&lt;/code&gt; немного отличаются по структуре, и &lt;code&gt;CPAN&lt;/code&gt; пришлось грубо допиливать. В конечном итоге он стал просто хранилищем, неким FTP сервером. Индексы так же считают на сторонней машине про cron и выкладывают в отдельный git репозиторий. На данный момент это самая большая библиотека модулей — их там больше 2700 версий.&lt;/p&gt;
  &lt;p id=&quot;w8B7&quot;&gt;Третьей попыткой, почти год назад, стал проект &lt;code&gt;fez&lt;/code&gt;. Если &lt;code&gt;CPAN&lt;/code&gt; предоставлял только возможности FTP сервера, то &lt;code&gt;fez&lt;/code&gt; решил идти дальше и добавил несколько полезных вещей. Таких, как улучшенная система авторизации, правильная работа с номерами версий модулей, мгновенная индексация на стороне репозитория. Плюс, так как это специальный проект, созданный именно для нужд &lt;code&gt;Raku&lt;/code&gt;, то он имеет отличные возможности для качественного развития.&lt;/p&gt;
  &lt;p id=&quot;CLrn&quot;&gt;Первое время я с настороженностью относился к &lt;code&gt;fez&lt;/code&gt;, но в последствии изменил своё мнение. Проект достаточно стабильный и отлично выполняет свои функции.&lt;/p&gt;
  &lt;p id=&quot;k5ZF&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;yE5Q&quot;&gt;Что хочется ещё&lt;/h3&gt;
  &lt;p id=&quot;9vhb&quot;&gt;Для успокоения души мне не хватает зеркал сервера &lt;code&gt;fez&lt;/code&gt;. Для чего нужно зеркало вообще?&lt;/p&gt;
  &lt;p id=&quot;DwiJ&quot;&gt;Дело в том, что мы живём во время, когда информационные сети стали неимоверно сложными, а желания их контролировать слишком высокими. Где-то специально, где-то случайно закрывается доступ к очень большим, важным и привычным ресурсам. Нет уверенности в том, что завтра утром ты сможешь получить доступ к своим данным, хранящимся где-то там. Благо это или нет, но это стало нашей сегодняшней реальностью. От сюда есть вполне естественное желание диверсифицировать риски. Один из способов — иметь зеркало библиотеки модулей.&lt;/p&gt;
  &lt;p id=&quot;15yR&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;sCG6&quot;&gt;Хочешь сделать что-то хорошо - сделай сам&lt;/h3&gt;
  &lt;p id=&quot;OqRD&quot;&gt;Хорошо сделать не обещаю, но так как альтернатив никаких, то сойдёт любое решение. Ниже я покажу как можно локально развернуть зеркало для модулей, хранящихся в &lt;code&gt;CPAN&lt;/code&gt; и &lt;code&gt;fez&lt;/code&gt; (&lt;code&gt;p6c&lt;/code&gt; используется малоактивно, и зеркалировать git хостинг это отдельная задача). Нам понадобится &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;sh&lt;/code&gt; и &lt;code&gt;docker&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;uj4k&quot;&gt;Исходя из логики работы &lt;code&gt;zef&lt;/code&gt;, принципиально нужно:&lt;/p&gt;
  &lt;ol id=&quot;QPa9&quot;&gt;
    &lt;li id=&quot;WtfO&quot;&gt;Скачать индексный файл;&lt;/li&gt;
    &lt;li id=&quot;OCY0&quot;&gt;Скачать все перечисленные в нём архивы с модулями;&lt;/li&gt;
    &lt;li id=&quot;1Qqk&quot;&gt;Повторить 1 и 2 для каждого репозитория;&lt;/li&gt;
    &lt;li id=&quot;P1hi&quot;&gt;Поднять и настроить nginx, чтобы он умел отдавать скачанные индексы и архивы;&lt;/li&gt;
    &lt;li id=&quot;M4TQ&quot;&gt;Сказать &lt;code&gt;zef&lt;/code&gt; по какому адресу лежит зеркало.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;9MQa&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;cbVL&quot;&gt;Скачиваем индексный файл&lt;/h3&gt;
  &lt;p id=&quot;iLEi&quot;&gt;Все настройки &lt;code&gt;zef&lt;/code&gt; лежат в файле &lt;code&gt;config.json&lt;/code&gt;. В документации к &lt;code&gt;zef&lt;/code&gt; сказано, чтобы узнать с каким дефолтным конфигом запускается &lt;code&gt;zef&lt;/code&gt;, нужно выполнить &lt;code&gt;zef --help&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;i1s1&quot; data-lang=&quot;bash&quot;&gt;&amp;gt; zef --help
[..]
CONFIGURATION ~/.rakubrew/versions/moar-2021.10/install/share/perl6/site/
resources/250C9FFF9756350CAA0F60C1873CDD80F2EB7A77.json
[..]

&amp;gt; cat ~/.rakubrew/versions/moar-2021.10/install/share/perl6/site/
resources/250C9FFF9756350CAA0F60C1873CDD80F2EB7A77.json
[..]
{
  &amp;quot;short-name&amp;quot;: &amp;quot;fez&amp;quot;,
  &amp;quot;enabled&amp;quot;: 1,
  &amp;quot;module&amp;quot;: &amp;quot;Zef::Repository::Ecosystems&amp;quot;,
  &amp;quot;options&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;fez&amp;quot;,
    &amp;quot;auto-update&amp;quot;: 1,
    &amp;quot;uses-path&amp;quot;: true,
    &amp;quot;mirrors&amp;quot;: [
      &amp;quot;http://360.zef.pm/&amp;quot;
    ]
  }
},&lt;/pre&gt;
  &lt;p id=&quot;Y1bL&quot;&gt;В разделе &lt;code&gt;mirrors&lt;/code&gt; конфигурации для &lt;code&gt;fez&lt;/code&gt; видим URI — то что нам нужно.&lt;/p&gt;
  &lt;pre id=&quot;CU4h&quot; data-lang=&quot;bash&quot;&gt;&amp;gt; mkdir -p ~/local-zef/local-fez &amp;amp;&amp;amp; cd ~/local-zef/local-fez
local-fez&amp;gt; curl https://360.zef.pm -o index.json&lt;/pre&gt;
  &lt;p id=&quot;memR&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Al7u&quot;&gt;Скачиваем архивы модулей&lt;/h3&gt;
  &lt;p id=&quot;HMhS&quot;&gt;В индексе &lt;code&gt;fez&lt;/code&gt; относительный путь архиву модуля лежит в поле &lt;code&gt;path&lt;/code&gt;. Например:&lt;/p&gt;
  &lt;pre id=&quot;1UuH&quot; data-lang=&quot;bash&quot;&gt;{
[..]
	&amp;quot;name&amp;quot;: &amp;quot;fez&amp;quot;,
	&amp;quot;path&amp;quot;: &amp;quot;F/EZ/FEZ/259a4732c97520938ce7310b3a1330db75bf91d0.tar.gz&amp;quot;,
	&amp;quot;perl&amp;quot;: &amp;quot;6.c&amp;quot;,
[..]
	&amp;quot;source-url&amp;quot;: &amp;quot;https://github.com/tony-o/raku-fez.git&amp;quot;,
	&amp;quot;version&amp;quot;: &amp;quot;17&amp;quot;
}&lt;/pre&gt;
  &lt;p id=&quot;bDmU&quot;&gt;Сейчас нужно вытащить все значения &lt;code&gt;path&lt;/code&gt; и составить один большой &lt;code&gt;curl&lt;/code&gt; запрос. Немного &lt;code&gt;sed-awk&lt;/code&gt;-магии:&lt;/p&gt;
  &lt;pre id=&quot;Vcu7&quot; data-lang=&quot;bash&quot;&gt;local-fez&amp;gt; grep -o &amp;#x27;&amp;quot;path&amp;quot;:&amp;quot;[^&amp;quot;]*&amp;quot;&amp;#x27; index.json | \
awk -F\&amp;quot; &amp;#x27;{print $4}&amp;#x27; | \
sed &amp;#x27;s|\(.*\)| -o \1 https://360.zef.pm/\1|g&amp;#x27; | \
awk -v d=&amp;quot;&amp;quot; &amp;#x27;{s=(NR==1?s:s d)$0}END{print s}&amp;#x27; | \
sed &amp;#x27;s/\(.*\)/curl -Z --create-dirs \1/&amp;#x27; | \
sh

DL% UL%  Dled  Uled  Xfers  Live   Qd Total     Current  Left    Speed
100 --  36.6M     0   623     0     0  0:00:06  0:00:11 --:--:-- 5440k

local-fez&amp;gt; ls

A  D  G  J  M  P  T  W
B  E  H  K  N  R  U  Y
C  F  I  L  O  S  V  index.json&lt;/pre&gt;
  &lt;p id=&quot;zmWK&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;uRPY&quot;&gt;Повторяем для CPAN&lt;/h3&gt;
  &lt;p id=&quot;7ShB&quot;&gt;Таким же образом можно скачать индекс и весь архив для &lt;code&gt;CPAN&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;4vOo&quot; data-lang=&quot;bash&quot;&gt;&amp;gt; mdkir -p ~/local-zef/local-cpan &amp;amp;&amp;amp; cd ~/local-zef/local-cpan
local-cpan&amp;gt; curl https://raw.githubusercontent.com/ugexe/Perl6-ecosystems/master/cpan1.json -o index.json
local-cpan&amp;gt; grep -o &amp;#x27;&amp;quot;source-url&amp;quot;: &amp;quot;[^&amp;quot;]*&amp;quot;&amp;#x27; index.json | \
awk -F\&amp;quot; &amp;#x27;{print $4}&amp;#x27; | \
sed &amp;#x27;s|http://www.cpan.org/authors/id/\(.*\)|\1|g&amp;#x27; | \
sed &amp;#x27;s|\(.*\)| -o \1 http://www.cpan.org/authors/id/\1|g&amp;#x27; | \
awk -v d=&amp;quot;&amp;quot; &amp;#x27;{s=(NR==1?s:s d)$0}END{print s}&amp;#x27; | \
sed &amp;#x27;s|\(.*\)|curl -Z --create-dirs \1|&amp;#x27; | \
sh

DL% UL%  Dled  Uled  Xfers  Live   Qd Total     Current  Left    Speed
100 --   337M     0  2767     0     0  0:00:54  0:00:41 --:--:-- 6308k

local-cpan&amp;gt; rm -rf ./git:

local-cpan&amp;gt; sed &amp;#x27;s|&amp;quot;source-url&amp;quot;: &amp;quot;http://www.cpan.org/authors/id|&amp;quot;source-url&amp;quot;: &amp;quot;http://localhost/local-cpan|g&amp;#x27; index.json &amp;gt; index-fix-uri.json

local-cpan&amp;gt; ls

A    G    N    V
B    H    P    W
C    J    R    Y
D    K    S    index-fix-uri.json
E    L    T    index.json
F    M    U&lt;/pre&gt;
  &lt;p id=&quot;7wcP&quot;&gt;Различия в ситуации со &lt;code&gt;CPAN&lt;/code&gt; следующие:&lt;/p&gt;
  &lt;ul id=&quot;LWJw&quot;&gt;
    &lt;li id=&quot;KpWB&quot;&gt;Путь к архиву абсолютный и лежит в поле &lt;code&gt;source-url&lt;/code&gt;. Соответственно, нам нужно подправить индексный файл, чтобы путь вёл не на &lt;code&gt;cpan.org&lt;/code&gt;, а на наш &lt;code&gt;localhost&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;8X62&quot;&gt;В индексе упоминаются несколько (около шести) устаревших модуля с неверными &lt;code&gt;source-url&lt;/code&gt;. Они порождают папку &lt;code&gt;git:&lt;/code&gt;, которая нам совершенно не нужна.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;WlKO&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;dCN4&quot;&gt;Поднимаем и настраиваем nginx&lt;/h3&gt;
  &lt;p id=&quot;foq8&quot;&gt;Для начала нужно написать конфигурационный файл для &lt;code&gt;nginx&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;t6Ka&quot; data-lang=&quot;bash&quot;&gt;cd ~/local-zef
local-zef&amp;gt; vim nginx.conf
[..]
local-zef&amp;gt; cat nginx.conf
events { worker_connections 1024; }
http {
  server {
    listen 80;
    root	/usr/share/nginx/html;
    location /local-fez { index /local-fez/index.json; }
    location /local-cpan { index /local-cpan/index-fix-uri.json; }
  }
}&lt;/pre&gt;
  &lt;p id=&quot;QJan&quot;&gt;Затем поднимаем &lt;code&gt;nginx&lt;/code&gt; в &lt;code&gt;docker&lt;/code&gt;, монтируем папку с архивами и индексами, и подкладываем нашу конфигурацию:&lt;/p&gt;
  &lt;pre id=&quot;S6hl&quot; data-lang=&quot;bash&quot;&gt;cd ~/local-zef
local-zef&amp;gt; docker run -it -p 80:80 \
-v /$PWD://usr/share/nginx/html:ro \
-v /$PWD/nginx.conf://etc/nginx/nginx.conf:ro \
--name=local-zef \
nginx:alpine&lt;/pre&gt;
  &lt;p id=&quot;pXXh&quot;&gt;После этих манипуляций у нас имеется поднятый web-сервер, который отдаст индексы для &lt;code&gt;fez&lt;/code&gt; и &lt;code&gt;cpan&lt;/code&gt; по адресам &lt;code&gt;http://localhost/local-zef&lt;/code&gt; и &lt;code&gt;http://localhost/local-cpan&lt;/code&gt; соответственно. Кроме того, с сервера можно скачать архивы по ссылкам из индекса.&lt;/p&gt;
  &lt;p id=&quot;8BX2&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;L4oQ&quot;&gt;Настраиваем zef&lt;/h3&gt;
  &lt;p id=&quot;r5m7&quot;&gt;Я предпочитаю завести отдельный конфиг для &lt;code&gt;zef&lt;/code&gt;. Относительно оригинального в нём нужно поменять пути до хранилища и временной папки, а так же имена и uri в разделах про &lt;code&gt;fez&lt;/code&gt; и &lt;code&gt;cpan&lt;/code&gt; репозитории: &lt;/p&gt;
  &lt;pre id=&quot;nJOD&quot; data-lang=&quot;bash&quot;&gt;cd ~/local-zef
local-zef&amp;gt; cp ~/.rakubrew/versions/moar-2021.10/install/share/perl6/site/resources/250C9FFF9756350CAA0F60C1873CDD80F2EB7A77.json zef.json
local-zef&amp;gt; vim zef.json
[..]
local-zef&amp;gt; cat zef.json
[..]
&amp;quot;StoreDir&amp;quot; : &amp;quot;$*HOME/.zef/store_local&amp;quot;,
&amp;quot;TempDir&amp;quot;  : &amp;quot;$*HOME/.zef/tmp_local&amp;quot;,
[..]
{
  &amp;quot;short-name&amp;quot;: &amp;quot;local-fez&amp;quot;,
  &amp;quot;enabled&amp;quot;: 1,
  &amp;quot;module&amp;quot;: &amp;quot;Zef::Repository::Ecosystems&amp;quot;,
  &amp;quot;options&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;local-fez&amp;quot;,
    &amp;quot;auto-update&amp;quot;: 1,
    &amp;quot;uses-path&amp;quot;: true,
    &amp;quot;mirrors&amp;quot;: [
      &amp;quot;http://127.0.0.1/local-fez/&amp;quot;
    ]
  }
}
[..]
{
  &amp;quot;short-name&amp;quot; : &amp;quot;local-cpan&amp;quot;,
  &amp;quot;enabled&amp;quot; : 1,
  &amp;quot;module&amp;quot; : &amp;quot;Zef::Repository::Ecosystems&amp;quot;,
  &amp;quot;options&amp;quot; : {
    &amp;quot;name&amp;quot; : &amp;quot;local-cpan&amp;quot;,
    &amp;quot;auto-update&amp;quot; : 1,
    &amp;quot;mirrors&amp;quot; : [
      &amp;quot;http://127.0.0.1/local-cpan/&amp;quot;
    ]
  }
}&lt;/pre&gt;
  &lt;p id=&quot;ZPhI&quot;&gt;Теперь мы можем установить модуль из локального зеркала таким образом:&lt;/p&gt;
  &lt;pre id=&quot;30bX&quot; data-lang=&quot;bash&quot;&gt;zef --config=~/local-zef/zef.json install LogP6&lt;/pre&gt;
  &lt;p id=&quot;Msuf&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;ZR8h&quot;&gt;Централизованный архив всех модулей Raku&lt;/h3&gt;
  &lt;p id=&quot;8tOZ&quot;&gt;Существует проект &lt;a href=&quot;https://github.com/lizmat/REA&quot; target=&quot;_blank&quot;&gt;REA&lt;/a&gt; (The Raku Programming Language Ecosystem Archive), в который регулярно собираются все когда-либо опубликованные модули. Кроме того, есть &lt;a href=&quot;https://raku.land/zef:lizmat/Ecosystem::Archive&quot; target=&quot;_blank&quot;&gt;специальный модуль&lt;/a&gt; Raku, который позволяет иметь похожий архив самостоятельно. Пока что &lt;code&gt;zef&lt;/code&gt; не умеет работать с этим архивом как источником данных, но есть смысл написать отдельный плагин.&lt;/p&gt;
  &lt;p id=&quot;WRam&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Wq2d&quot;&gt;Заключение&lt;/h3&gt;
  &lt;p id=&quot;7nNY&quot;&gt;Я очень надеюсь, что проект &lt;code&gt;fez&lt;/code&gt; будет продолжать активно развиваться. По &lt;a href=&quot;https://deathbyperl6.com/faq-zef-ecosystem/&quot; target=&quot;_blank&quot;&gt;словам автора&lt;/a&gt;, сейчас fez это набор из AWS-ламбда, S3 и Сloudfront, но никак не код. Думаю, есть смысл написать реализацию &lt;code&gt;fez&lt;/code&gt; в коде. Тогда можно будет легко разворачивать реальные альтернативные сервера, например для корпоративных целей. Пожелаем удачи!&lt;/p&gt;
  &lt;blockquote id=&quot;9E5s&quot; data-align=&quot;center&quot;&gt;&lt;a href=&quot;https://translate.google.com/translate?hl=&amp;sl=ru&amp;tl=en&amp;u=rakurs.atroxaper.net%2F2021-11-30-mirroring-zef-repository&quot; target=&quot;_blank&quot;&gt;English version&lt;/a&gt;&lt;/blockquote&gt;
  &lt;tt-tags id=&quot;wuwy&quot;&gt;
    &lt;tt-tag name=&quot;rakulang&quot;&gt;#rakulang&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;удачныйракурс&quot;&gt;#удачныйракурс&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;zef&quot;&gt;#zef&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;fez&quot;&gt;#fez&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;repository&quot;&gt;#repository&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;modules&quot;&gt;#modules&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;docker&quot;&gt;#docker&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;nginx&quot;&gt;#nginx&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;

</content></entry><entry><id>goodrakurs:2021-10-23-code-coverage</id><link rel="alternate" type="text/html" href="https://rakurs.atroxaper.net/2021-10-23-code-coverage?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=goodrakurs"></link><title>Измерение уровня покрытия кода тестами в Raku | Code coverage level measurement in Raku</title><published>2021-11-27T17:05:06.850Z</published><updated>2021-12-01T02:25:42.744Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/fe/04/fe042a5b-09a1-46ca-88fd-06619b7da335.jpeg"></media:thumbnail><tt:hashtag>rakulang</tt:hashtag><tt:hashtag>удачныйракурс</tt:hashtag><tt:hashtag>codecoverage</tt:hashtag><tt:hashtag>racoco</tt:hashtag><tt:hashtag>покрытиекода</tt:hashtag><tt:hashtag>тестирование</tt:hashtag><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/c6/13/c6138c75-f547-4a77-8990-a69289750308.jpeg&quot;&gt;Легко заметить одну особенность в культуре написания модулей на Raku и Perl — почти всегда присутствует папка t с авто тестами. Даже в маленьких проектах. Конечно, количество и качество тестов может варьироваться, но чаще всего они есть.</summary><content type="html">
  &lt;figure id=&quot;jZ2b&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c6/13/c6138c75-f547-4a77-8990-a69289750308.jpeg&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;5Bbp&quot;&gt;Легко заметить одну особенность в культуре написания модулей на &lt;code&gt;Raku&lt;/code&gt; и &lt;code&gt;Perl&lt;/code&gt; — почти всегда присутствует папка &lt;code&gt;t&lt;/code&gt; с авто тестами. Даже в маленьких проектах. Конечно, количество и качество тестов может варьироваться, но чаще всего они есть.&lt;/p&gt;
  &lt;p id=&quot;S04V&quot;&gt;Когда мы говорим об авто тестировании, возникает желание как-то оценить «качество» написанных тестов. Одним из критериев может являться процент исполненных строк (веток) кода во время успешного прохождения тестов. На данный момент для &lt;code&gt;Raku&lt;/code&gt; это можно сделать двумя способами. &lt;/p&gt;
  &lt;p id=&quot;Vn4c&quot;&gt;Первый — с помощью &lt;code&gt;CommaIDE&lt;/code&gt; (&lt;a href=&quot;https://commaide.com&quot; target=&quot;_blank&quot;&gt;https://commaide.com&lt;/a&gt;), IDE для разработки на языке &lt;code&gt;Raku&lt;/code&gt;. &lt;code&gt;CommaIDE&lt;/code&gt; это отличная вещь, особенно, после последних релизов. Я настоятельно рекомендую её попробовать. В платной версии IDE есть возможность запустить тесты, и посчитать процент покрытия кода. Кроме того, для наглядности, код раскрасится в соответствующие цвета.&lt;/p&gt;
  &lt;p id=&quot;uyhu&quot;&gt;Второй — использовать модуль &lt;code&gt;App::RaCoCo&lt;/code&gt; (Raku Code Coverage). Он поставляет консольное приложение &lt;code&gt;racoco&lt;/code&gt;, которое запускает тесты, и подсчитывает процент покрытия. Сегодня хочется рассказать об этом модуле подробнее.&lt;/p&gt;
  &lt;p id=&quot;tSN3&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Cknh&quot;&gt;Как это работает в теории&lt;/h3&gt;
  &lt;p id=&quot;WEPH&quot;&gt;Чтобы посчитать покрытие, нам нужно знать две вещи: какие именно строки кода потенциально могут выполниться, и фактически выполненные во время прохождения тестов.&lt;/p&gt;
  &lt;blockquote id=&quot;0IMG&quot;&gt;NB: существуют несколько бекендов для компилятора &lt;code&gt;Raku&lt;/code&gt; (например, &lt;code&gt;jvm&lt;/code&gt;, &lt;code&gt;js&lt;/code&gt;, &lt;code&gt;moarvm&lt;/code&gt;). На данный момент предоставить всю необходимую информацию может только &lt;code&gt;MoarVM&lt;/code&gt;. Кроме того, более информативно считать не выполненные строки кода, а ветви. Например, в одной строке&lt;br /&gt;&lt;code&gt;say &amp;#x27;second&amp;#x27; if $first;&lt;br /&gt;&lt;/code&gt;две ветви. К сожалению, сейчас даже &lt;code&gt;MoarVM&lt;/code&gt; не предоставляет информации о выполненных ветвях. По-этому, далее будет говориться только о выполненных строках.&lt;/blockquote&gt;
  &lt;p id=&quot;w5nc&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;nyly&quot;&gt;Как это работает на практике&lt;/h3&gt;
  &lt;p id=&quot;c33o&quot;&gt;Чтобы получить список потенциально исполняемых строк кода некоторого файла, нужно:&lt;/p&gt;
  &lt;ol id=&quot;Cwaw&quot;&gt;
    &lt;li id=&quot;3Nxj&quot;&gt;получить байт-код этого файла. Его можно или найти в папке &lt;code&gt;lib/.precomp&lt;/code&gt;, или скомпилировать самостоятельно. Например,&lt;br /&gt;&lt;code&gt;raku -Ilib --target=mbc --output=result lib/source-file.rakumod&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;jFE2&quot;&gt;снять дамп с этого файла командой &lt;code&gt;moar --dump result&lt;/code&gt;. Вхождения, начинающиеся со слова &lt;code&gt;annotation&lt;/code&gt;, заканчиваются номером строки, которая может быть исполнена в соответствующем модуле. Например:&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;F7gS&quot;&gt;[...]
00013      bindlex            lex_Frame_9_self_obj, loc_19_obj
00014      param_sn           loc_4_obj
     annotation: lib/source-file.rakumod (MyModuleName):9
00015      getcode            loc_1_obj, Frame_10
00016      getcode            loc_2_obj, Frame_11
[...]&lt;/pre&gt;
  &lt;p id=&quot;u2t4&quot;&gt;Чтобы получить данные о том, какие строки выполнились, нужно:&lt;/p&gt;
  &lt;ol id=&quot;TTCC&quot;&gt;
    &lt;li id=&quot;XlhQ&quot;&gt;положить в переменные окружения специальный флаг &lt;code&gt;MVM_COVERAGE_LOG&lt;/code&gt;. Его значением должен являться путь к файлу с будущими логами исполнения кода;&lt;/li&gt;
    &lt;li id=&quot;QRFr&quot;&gt;запустить тесты;&lt;/li&gt;
    &lt;li id=&quot;AbiR&quot;&gt;разобрать файл с логами. Нас интересуют только строки с файлами из тестируемой библиотеки. Например:&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;O3m7&quot;&gt;HIT  src/vm/moar/ModuleLoader.nqp  133
HIT  src/vm/moar/ModuleLoader.nqp  5
HIT  lib/source-file.rakumod (MyModuleName)  1
HIT  lib/source-file.rakumod (MyModuleName)  9
HIT  gen/moar/World.nqp  5240
HIT  gen/moar/World.nqp  5240&lt;/pre&gt;
  &lt;p id=&quot;zJwp&quot;&gt;Имея эти данные и немного знаний об арифметике можно посчитать результат.&lt;/p&gt;
  &lt;p id=&quot;8SeK&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;gFbg&quot;&gt;Как это использовать&lt;/h3&gt;
  &lt;p id=&quot;pKZW&quot;&gt;Рассмотрим несколько типичных вариантов использования &lt;code&gt;racoco&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;GlCO&quot;&gt;MyModyle&amp;gt; racoco
t/00-simple.t ................... ok 
t/01-difficult.t ................ ok  
All tests successful.
Files=2, Tests=54,  3 wallclock secs
Result: PASS                        # prove6 result 
Coverage: 70.6%                     # code coverage percentage

MyModule&amp;gt; racoco --exec=&amp;#x27;prove --exec=raku&amp;#x27;
t/00-simple.t ................... ok 
t/01-difficult.t ................ ok
All tests successful.
Files=2, Tests=54, 3 wallclock secs
Result: PASS                        # prove result
Coverage: 70.8%

MyModule&amp;gt; racoco --exec=&amp;#x27;prove6 -l&amp;#x27; # this is for who do not write
                                    # ｢use lib &amp;#x27;lib&amp;#x27;;｣ in tests
[...]

MyModule&amp;gt; racoco --silent
Coverage: 70.8%          # just what it was all about

MyModule&amp;gt; racoco --html  # HTML with statistics and colored code
Visualisation: file://.racoco/report.html
Coverage: 70.8%&lt;/pre&gt;
  &lt;figure id=&quot;ApmV&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5b/76/5b76781f-2210-4842-a1aa-d1628b296af9.png&quot; width=&quot;560.0000000000001&quot; /&gt;
    &lt;figcaption&gt;Список файлов модуля со статистикой&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;pre id=&quot;50qN&quot;&gt;MyModule&amp;gt; racoco --/exec --html --color-blind
            # don&amp;#x27;t run tests, use the latest data
            # and color code for colorblind people&lt;/pre&gt;
  &lt;figure id=&quot;OVKs&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4a/e1/4ae15e0a-7baf-4dc0-a83d-cdcab46a907b.png&quot; width=&quot;563&quot; /&gt;
    &lt;figcaption&gt;Раскрашеный код для дальтоников&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;pre id=&quot;958R&quot;&gt;MyModule&amp;gt; racoco --fail-level=93   # the launch should fail
[...]                              # in case of low coverage
Coverage: 70.8%      # exit code: 23 (93 - 70 = 23)

MyModule&amp;gt; racoco --silent
Coverage: 70.8%
MyModule&amp;gt; racoco --silent --append --exec=&amp;#x27;prove6 xt&amp;#x27;
Coverage: 95.2%  # combined coverage of both launches
                 # similarly --exec=&amp;#x27;prove6 t xt&amp;#x27;

/root/  &amp;gt; racoco --lib=&amp;#x27;/home/user/raku/MyModule/lib&amp;#x27; \
                 --exec=&amp;#x27;prove6 /home/user/raku/MyModule/t&amp;#x27;
                 # launch not from the module folder

MyModule&amp;gt; racoco --raku-bin-dir=&amp;#x27;/home/user/hack-rakudo/install/bin&amp;#x27;
               # explicit indication of the path to raku and moar&lt;/pre&gt;
  &lt;h3 id=&quot;Ofwd&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;iLSN&quot;&gt;Неочевидный момент&lt;/h3&gt;
  &lt;p id=&quot;eSvw&quot;&gt;&lt;code&gt;racoco&lt;/code&gt; может выбросить сообщение об ошибке про неочевидное содержимое папки &lt;code&gt;.precomp&lt;/code&gt;. Это происходит из-за того, что папка содержит подпапки для каждой использованной версии компилятора. В общем случае, пользователь может выбрать любую версию компилятора для запуска тестов. При этом, если нет необходимости, скомпилированные ранее файлы могут быть не обновлены. Таким образом, &lt;code&gt;racoco&lt;/code&gt; не может угадать какую папку анализировать. Для подобных случаев существует флаг &lt;code&gt;--fix-compunit&lt;/code&gt;, который удалит всё содержимое папки &lt;code&gt;.precomp&lt;/code&gt; самостоятельно (если вам дорого время компиляции, то можно уничтожить неактуальные версии скомпилированных файлов вручную).&lt;/p&gt;
  &lt;p id=&quot;U8Bc&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;7Guv&quot;&gt;Немного про CI&lt;/h3&gt;
  &lt;p id=&quot;M1hq&quot;&gt;Невозможно говорить про авто тесты и покрытие кода и не заговорить про CI. В этом контексте измерение покрытия кода может использоваться для нескольких вещей:&lt;/p&gt;
  &lt;ul id=&quot;KDbt&quot;&gt;
    &lt;li id=&quot;Ojeu&quot;&gt;Падение сборки в случае снижения покрытия ниже определённого уровня;&lt;/li&gt;
    &lt;li id=&quot;Ahc3&quot;&gt;Падение сборки в случае снижения покрытия относительно предыдущих значений;&lt;/li&gt;
    &lt;li id=&quot;rKc8&quot;&gt;Простое измерение покрытия кода как характеристики.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;c3M5&quot;&gt;Для примера, рассмотрим то, как можно добавить шилдик об уровне покрытия кода в &lt;code&gt;README&lt;/code&gt; файл на &lt;code&gt;GitHub&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;cb9t&quot;&gt;Есть замечательная &lt;a href=&quot;https://raku-advent.blog/2020/12/13/day-13-helping-the-github-action-elves/&quot; target=&quot;_blank&quot;&gt;статья&lt;/a&gt; о том, как настроить автоматическую проверку кода тестами с помощью &lt;code&gt;GitHub Actions&lt;/code&gt; для проектов на &lt;code&gt;Raku&lt;/code&gt;. Первый YAML из этой статьи отлично работает. Нужно заменить последний шаг (Run Tests), на следующую последовательность:&lt;/p&gt;
  &lt;ol id=&quot;zNG5&quot;&gt;
    &lt;li id=&quot;w5GG&quot;&gt;Установить &lt;code&gt;App::RaCoCo&lt;/code&gt;. Этот модуль намеренно не содержит никаких зависимостей, чтобы его установка занимала как можно меньше времени;&lt;br /&gt;&lt;code&gt;- name: Install App::RaCoCO&lt;br /&gt;  run: zef install --/test App::RaCoCo&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;D4xr&quot;&gt;Запустить &lt;code&gt;racoco&lt;/code&gt;;&lt;br /&gt;&lt;code&gt;- name: Run App::RaCoCo&lt;br /&gt;  run: racoco&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;WbmM&quot;&gt;Вытащить результат работы &lt;code&gt;racoco&lt;/code&gt; из файла &lt;code&gt;.racoco/report.txt&lt;/code&gt;. Нам нужна только первая строчка — процент покрытия. Убирается дробную часть и знак процента. Результат складываем во временную переменную окружения сборки &lt;code&gt;COVERAGE&lt;/code&gt;;&lt;br /&gt;&lt;code&gt;- name: Discover Code Coverage Level&lt;br /&gt;  run: |&lt;br /&gt;        coverage=&lt;/code&gt;head -1 .racoco/report.txt | sed &amp;#x27;s/\.*//&amp;#x27;&lt;code&gt;&lt;br /&gt;        echo &amp;quot;COVERAGE=$coverage%&amp;quot; &amp;gt;&amp;gt; $GITHUB_ENV&lt;br /&gt;  shell: bash&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;6lv8&quot;&gt;Для построения шилдика используем &lt;a href=&quot;https://github.com/marketplace/actions/dynamic-badges&quot; target=&quot;_blank&quot;&gt;плагин&lt;/a&gt; &lt;code&gt;schneegans/dynamic-badges-action&lt;/code&gt;;&lt;br /&gt;&lt;code&gt;- name: Create Code Coverage Badge&lt;br /&gt;  uses: schneegans/dynamic-badges-action@v1.1.0&lt;br /&gt;  with:&lt;br /&gt;        auth: $\{\{ secrets.&amp;lt;YOUR_TOCKEN&amp;gt; }}&lt;br /&gt;        gistID: &amp;lt;gist-ID&amp;gt;&lt;br /&gt;        filename: &amp;lt;you-gist-file&amp;gt;.json&lt;br /&gt;        label: Coverage&lt;br /&gt;        message: $\{\{ env.COVERAGE }}&lt;br /&gt;&lt;/code&gt;5. Добавляем строчку со ссылкой на шилдик в README.md.&lt;br /&gt;&lt;code&gt;![badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/&amp;lt;user&amp;gt;/&amp;lt;gist-id&amp;gt;/raw/&amp;lt;you-gist-file&amp;gt;.json)&lt;/code&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;r3Qy&quot;&gt;Пример &lt;a href=&quot;https://github.com/atroxaper/raku-RaCoCo/blob/master/.github/workflows/code-coverage.yml&quot; target=&quot;_blank&quot;&gt;почти такого YAML&lt;/a&gt; и итоговый &lt;a href=&quot;https://github.com/atroxaper/raku-RaCoCo/blob/master/README.md&quot; target=&quot;_blank&quot;&gt;шилдик&lt;/a&gt; можно посмотреть в &lt;a href=&quot;https://github.com/atroxaper/raku-RaCoCo&quot; target=&quot;_blank&quot;&gt;репозитории&lt;/a&gt; &lt;code&gt;App::RaCoCo&lt;/code&gt;.&lt;/p&gt;
  &lt;figure id=&quot;8joc&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c8/82/c882a503-2789-41b4-b37e-bff994648a70.png&quot; width=&quot;551&quot; /&gt;
    &lt;figcaption&gt;Наглядный уровень покрытия&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;blockquote id=&quot;zfHt&quot; data-align=&quot;center&quot;&gt;&lt;a href=&quot;https://translate.google.com/translate?hl=&amp;sl=ru&amp;tl=en&amp;u=rakurs.atroxaper.net%2F2021-10-23-code-coverage&quot; target=&quot;_blank&quot;&gt;English version&lt;/a&gt;&lt;/blockquote&gt;
  &lt;tt-tags id=&quot;Ag5S&quot;&gt;
    &lt;tt-tag name=&quot;rakulang&quot;&gt;#rakulang&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;удачныйракурс&quot;&gt;#удачныйракурс&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;codecoverage&quot;&gt;#codecoverage&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;racoco&quot;&gt;#racoco&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;покрытиекода&quot;&gt;#покрытиекода&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;тестирование&quot;&gt;#тестирование&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;

</content></entry><entry><id>goodrakurs:2021-02-13-contributing-raku</id><link rel="alternate" type="text/html" href="https://rakurs.atroxaper.net/2021-02-13-contributing-raku?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=goodrakurs"></link><title>Контрибутинг в Raku для самых маленьких | Raku contributing for the little ones</title><published>2021-11-27T14:09:40.430Z</published><updated>2021-12-01T02:25:02.027Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/36/d2/36d2b10c-97b3-40cd-81c6-94464cbd31b0.jpeg"></media:thumbnail><tt:hashtag>rakulang</tt:hashtag><tt:hashtag>удачныйракурс</tt:hashtag><tt:hashtag>opensource</tt:hashtag><tt:hashtag>mmorpg</tt:hashtag><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/46/69/46696c3f-9115-4cb0-8c95-c2108c812178.jpeg&quot;&gt;В последние пару недель я несколько раз натыкался на статьи и видео о том, как это здорово — контрибутить в открытое ПО. По этому поводу я вспомнил о другой старой статье «Raku это моя MMORPG». В ней говорится, что приносить пользу открытому ПО можно несколькими способами. Например, можно быть Воином и писать программы, основанные на каком-то открытом ПО. Можно быть Лучником — и писать блоги, твиты и подобное, возбуждая интерес с выбранному ПО. Ещё, можно быть Магом — реализовывать новые фичи и фиксить баги. Сегодня я возьму Лучника и расскажу, как можно стать Магом для языка программирования Raku.</summary><content type="html">
  &lt;figure id=&quot;t8Gy&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/46/69/46696c3f-9115-4cb0-8c95-c2108c812178.jpeg&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;EiRZ&quot;&gt;В последние пару недель я несколько раз натыкался на &lt;a href=&quot;https://mydeveloperplanet.com/2021/01/20/how-to-start-contributing-to-open-source/&quot; target=&quot;_blank&quot;&gt;статьи&lt;/a&gt; и &lt;a href=&quot;https://youtu.be/GAqfMNB-YBU&quot; target=&quot;_blank&quot;&gt;видео&lt;/a&gt; о том, как это здорово — контрибутить в открытое ПО. По этому поводу я вспомнил о другой старой статье &lt;a href=&quot;http://strangelyconsistent.org/blog/perl-6-is-my-mmorpg&quot; target=&quot;_blank&quot;&gt;«Raku это моя MMORPG»&lt;/a&gt;. В ней говорится, что приносить пользу открытому ПО можно несколькими способами. Например, можно быть Воином и писать программы, основанные на каком-то открытом ПО. Можно быть Лучником — и писать блоги, твиты и подобное, возбуждая интерес с выбранному ПО. Ещё, можно быть Магом — реализовывать новые фичи и фиксить баги. Сегодня я возьму Лучника и расскажу, как можно стать Магом для языка программирования &lt;code&gt;&lt;a href=&quot;https://raku.org&quot; target=&quot;_blank&quot;&gt;Raku&lt;/a&gt;&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;kL54&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;QTMf&quot;&gt;Выбираем квест&lt;/h3&gt;
  &lt;p id=&quot;niVw&quot;&gt;Давайте выберем какой-нибудь баг компилятора и пофиксим его. Идём в &lt;a href=&quot;https://github.com/rakudo/rakudo/issues&quot; target=&quot;_blank&quot;&gt;трекер&lt;/a&gt; компилятора &lt;code&gt;&lt;a href=&quot;https://rakudo.org&quot; target=&quot;_blank&quot;&gt;Rakudo&lt;/a&gt;&lt;/code&gt; и выбираем подходящую багу. Я полистал список меток и наткнулся на &lt;strong&gt;parsing&lt;/strong&gt; — удача, я какое-то время назад разбирался в грамматике компилятора и штудировал &lt;a href=&quot;https://www.apress.com/gp/book/9781484232279&quot; target=&quot;_blank&quot;&gt;отличную книжку&lt;/a&gt; по этой теме. Есть четыре задачки:&lt;/p&gt;
  &lt;figure id=&quot;g8fH&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/72/37/72370c5d-995f-469b-aebc-5f9a122c90c4.png&quot; width=&quot;1718&quot; /&gt;
    &lt;figcaption&gt;Список открытых задач с меткой parsing&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;ol id=&quot;VCcw&quot;&gt;
    &lt;li id=&quot;et5i&quot;&gt;С меткой LTA (Less Than Awesome — чуть менее, чем потрясающе — когда настоящее поведение отличается от интуитивно ожидаемого) — пока вычёркиваем;&lt;/li&gt;
    &lt;li id=&quot;kd52&quot;&gt;С меткой «нужен консенсус» — мы хотим просто пофиксить несложную багу — точно вычёркиваем;&lt;/li&gt;
    &lt;li id=&quot;51UH&quot;&gt;С меткой «grammar and actions» про возможно мёртвый код — хороший кандидат для первой задачки;&lt;/li&gt;
    &lt;li id=&quot;w8Ld&quot;&gt;Просто «parsing» и что-то про метаоператор R — идеально, берём.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;IRBf&quot;&gt;С &lt;a href=&quot;https://github.com/rakudo/rakudo/issues/1632&quot; target=&quot;_blank&quot;&gt;квестом&lt;/a&gt; определились, теперь нужно настроить рабочую среду. В Windows, Linux и macOS всё должно быть примерно одинаково. Я буду показывать на macOS.&lt;/p&gt;
  &lt;p id=&quot;AYe4&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;nuwR&quot;&gt;Настройка рабочей среды&lt;/h3&gt;
  &lt;p id=&quot;wFfF&quot;&gt;Создаём папки для исходных кодов и для собранного компилятора:&lt;/p&gt;
  &lt;pre id=&quot;tK8z&quot; data-lang=&quot;bash&quot;&gt;mkdir ~/dev-rakudo &amp;amp;&amp;amp; mkdir ~/dev-rakudo-install&lt;/pre&gt;
  &lt;p id=&quot;tQpB&quot;&gt;Компилятор &lt;code&gt;Rakudo&lt;/code&gt; состоит из трёх компонентов:&lt;/p&gt;
  &lt;ol id=&quot;fEFI&quot;&gt;
    &lt;li id=&quot;oJBF&quot;&gt;Виртуальная машина. Сейчас их есть три — &lt;code&gt;JVM&lt;/code&gt;, &lt;code&gt;JS&lt;/code&gt; и &lt;code&gt;MoarVM&lt;/code&gt;. Мы берём &lt;code&gt;MoarVM&lt;/code&gt;, как самую стабильную;&lt;/li&gt;
    &lt;li id=&quot;4Wjz&quot;&gt;Реализация низкоуровневого (промежуточного) языка &lt;code&gt;NQP&lt;/code&gt; (Not Quite Perl) — это некоторое «подмножество» языка &lt;code&gt;Raku&lt;/code&gt;. Виртуальная машина как раз позволяет исполнять код, написанный на &lt;code&gt;NQP&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;Nf4s&quot;&gt;Сам компилятор &lt;code&gt;Rakudo&lt;/code&gt;, написанный на &lt;code&gt;NQP&lt;/code&gt; и &lt;code&gt;Raku&lt;/code&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;MS4Y&quot;&gt;Скачиваем и компилируем все три компонента. Их сборка заняла у меня полторы, пол и две с половиной минуты соответственно:&lt;/p&gt;
  &lt;pre id=&quot;DepF&quot; data-lang=&quot;bash&quot;&gt;cd ~/dev-rakudo &amp;amp;&amp;amp; git clone git@github.com:MoarVM/MoarVM.git &amp;amp;&amp;amp; cd MoarVM
perl Configure.pl --prefix ~/dev-rakudo-install &amp;amp;&amp;amp; make -j 4 &amp;amp;&amp;amp; make install

cd ~/dev-rakudo &amp;amp;&amp;amp; git clone git@github.com:Raku/nqp.git &amp;amp;&amp;amp; cd nqp
perl Configure.pl --backend=moar --prefix ~/dev-rakudo-install &amp;amp;&amp;amp; make -j 4 &amp;amp;&amp;amp; make install

cd ~/dev-rakudo &amp;amp;&amp;amp; git clone git@github.com:rakudo/rakudo.git &amp;amp;&amp;amp; cd rakudo
perl Configure.pl --backend=moar --prefix ~/dev-rakudo-install &amp;amp;&amp;amp; make -j 4 &amp;amp;&amp;amp; make install&lt;/pre&gt;
  &lt;p id=&quot;lZBY&quot;&gt;Обратите внимание на параметры: &lt;code&gt;--prefix&lt;/code&gt; — показывает, куда будут скопированы исполняемые файлы после команды &lt;code&gt;make install&lt;/code&gt;, &lt;code&gt;--backend=moar &lt;/code&gt;указывает на используемую виртуальную машину, а &lt;code&gt;-j 4&lt;/code&gt; просит распараллеливать работу на несколько потоков (вдруг ускорит). Теперь у нас есть собранный компилятор &lt;code&gt;Rakudo&lt;/code&gt; &lt;code&gt;~/dev-rakudo-install/bin/raku&lt;/code&gt;. Так же нам понадобится официальный сборник тестов для компилятора. Их нужно положить в папку с его кодом:&lt;/p&gt;
  &lt;pre id=&quot;Lbz4&quot; data-lang=&quot;bash&quot;&gt;cd ~/dev-rakudo/rakudo &amp;amp;&amp;amp; git clone https://github.com/Raku/roast.git t/spec&lt;/pre&gt;
  &lt;p id=&quot;vN1d&quot;&gt;Первым делом прогоним тесты. Это обычная ситуация, когда какие-то тесты не проходят ещё до новых изменений. Нам нужно их выявить, чтобы потом не было опасений, что изменения сломали что-то лишнее:&lt;/p&gt;
  &lt;blockquote id=&quot;XX2P&quot;&gt;Здесь и дальше я буду работать в папке &lt;code&gt;~/dev-rakudo/rakudo&lt;/code&gt;, если не указано иное.&lt;/blockquote&gt;
  &lt;pre id=&quot;3HbO&quot; data-lang=&quot;bash&quot;&gt;&amp;gt; make spectest
[...]
Test Summary Report
-------------------
t/spec/S32-str/utf8-c8.t    (Wstat: 65280 Tests: 54 Failed: 0)
  Non-zero exit status: 255
  Parse errors: Bad plan.  You planned 66 tests but ran 54.
Files=1346, Tests=117144, 829 wallclock secs (27.70 usr  6.04 sys + 2638.79 cusr 210.98 csys = 2883.51 CPU)
Result: FAIL
make: *** [m-spectest5] Error 1&lt;/pre&gt;
  &lt;p id=&quot;IhOU&quot;&gt;Прошло 117,144 теста в 1,346 файлах за 14 минут. Несколько тестов, относящихся к &lt;code&gt;utf8&lt;/code&gt; по какой-то причине не исполнились, всё остальное работает как надо. Мы готовы к работе!&lt;/p&gt;
  &lt;p id=&quot;HJCA&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;R2JD&quot;&gt;Разбираемся в постановке задачи&lt;/h3&gt;
  &lt;p id=&quot;RYEQ&quot;&gt;В задаче написано, что какой-то метаоператор &lt;code&gt;R&lt;/code&gt; что-то делает не так с &lt;code&gt;colonpair&lt;/code&gt;. Открываю &lt;a href=&quot;https://docs.raku.org&quot; target=&quot;_blank&quot;&gt;документацию&lt;/a&gt; и ищу по слову &lt;code&gt;R&lt;/code&gt; — метаоператоров с таким именем в выпадающем списке нет. Пробую набрать &lt;code&gt;metaop&lt;/code&gt; и вижу &lt;a href=&quot;https://docs.raku.org/language/operators#index-entry-R_reverse_metaoperator&quot; target=&quot;_blank&quot;&gt;reverse metaoperator (R)&lt;/a&gt;. Оказывается, если вам хочется написать операнды бинарной операции в обратном порядке, то можно использовать префикс &lt;code&gt;R&lt;/code&gt; перед её символом:&lt;/p&gt;
  &lt;pre id=&quot;5kZ3&quot;&gt;say 3 R- 2 == -1; # Output: True&lt;/pre&gt;
  &lt;p id=&quot;HBJM&quot;&gt;&lt;code&gt;Colonpair&lt;/code&gt; это синтаксис для обозначения именованной пары. Выглядит он как имя, следующее перед двоеточием и предшествующее круглым скобкам со значением. Например &lt;code&gt;:foo(42)&lt;/code&gt; это пара с именем &lt;code&gt;foo&lt;/code&gt; и значением &lt;code&gt;42&lt;/code&gt;. Такой синтаксис часто используется, чтобы передать значение в &lt;a href=&quot;https://docs.raku.org/type/Signature#Positional_vs._named_arguments&quot; target=&quot;_blank&quot;&gt;именованный параметр функции&lt;/a&gt; при её вызове:&lt;/p&gt;
  &lt;pre id=&quot;MI9w&quot;&gt;sub sub-with-named-parameter(:$foo) {
  say $foo;
}

sub-with-named-parameter(:foo(42)); # Output: 42&lt;/pre&gt;
  &lt;p id=&quot;BMkt&quot;&gt;Если параметр функции будет не именованным, а позиционным, то при вызове его с именованной парой случится ошибка компиляции:&lt;/p&gt;
  &lt;pre id=&quot;Nz9q&quot;&gt;sub sub-without-named-parameter($foo) { # &amp;lt;- нет двоеточия
  say $foo;
}

sub-without-named-parameter(:foo(42)); # Unexpected named argument &amp;#x27;foo&amp;#x27; passed&lt;/pre&gt;
  &lt;p id=&quot;nmpc&quot;&gt;Если при вызове такой функции окружить аргумент скобками, то в позиционный параметр уйдёт вся пара целиком:&lt;/p&gt;
  &lt;pre id=&quot;WD4f&quot;&gt;sub sub-without-named-parameter($foo) {
  say $foo;
}

sub-without-named-parameter((:foo(42))); # Output: foo =&amp;gt; 42&lt;/pre&gt;
  &lt;p id=&quot;K5By&quot;&gt;В &lt;code&gt;Raku&lt;/code&gt; можно написать функцию, перехватывающую все аргументы, которые в неё передали и проанализировать. Делается это с помощью вертикальной черты перед единственным параметром — &lt;a href=&quot;https://docs.raku.org/type/Capture&quot; target=&quot;_blank&quot;&gt;Capture&lt;/a&gt;:&lt;/p&gt;
  &lt;pre id=&quot;9iR9&quot;&gt;sub sub-with-capture(|foo) { # &amp;lt;- параметр Capture
  say foo;
}

sub-with-capture(:foo(42));     # Output: \(:foo(42))
sub-with-capture(42);           # Output: \(42)
sub-with-capture(:foo(3 Z- 2)); # Output: \(:foo((1,).Seq))
sub-with-capture(:foo(3 R- 2)); # Output: \(-1)&lt;/pre&gt;
  &lt;p id=&quot;eP1w&quot;&gt;В предпоследней строке используется метаоператор &lt;code&gt;Z&lt;/code&gt; — &lt;a href=&quot;https://docs.raku.org/language/operators#index-entry-Z_(zip_metaoperator)&quot; target=&quot;_blank&quot;&gt;zip-оператор&lt;/a&gt;. Он воспринимает правую и левую часть как список, последовательно берёт из них по элементу и применяет операцию, в результате получая &lt;a href=&quot;https://docs.raku.org/type/Seq&quot; target=&quot;_blank&quot;&gt;последовательность&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;OPVM&quot;&gt;В последней строке используется как раз нужный нам метаоператор &lt;code&gt;R&lt;/code&gt;. В этом случае в функцию передалась не пара, а константа. Можно было бы предположить, что это некая особенность работы метаоператоров, но пример с &lt;code&gt;Z&lt;/code&gt; показывает, что это не так. Собственно, в этом и заключается баг — при передаче в функцию &lt;code&gt;colonpair&lt;/code&gt;, использующую метаоператор &lt;code&gt;R&lt;/code&gt;, пара превращается её значение.&lt;/p&gt;
  &lt;p id=&quot;9sMK&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Twda&quot;&gt;Нужен новый тест&lt;/h3&gt;
  &lt;p id=&quot;mIiy&quot;&gt;Чтобы убедиться в том, что будущие изменения исправят неверное поведение, нужно написать новый тест. В тестовых файлах несложно найти тест (&lt;a href=&quot;https://github.com/Raku/roast/blob/fea1d16d993eb851d2935155e0b0d074fa3593bf/S03-metaops/reverse.t&quot; target=&quot;_blank&quot;&gt;S03-metaops/reverse.t&lt;/a&gt;) метаоператора &lt;code&gt;R&lt;/code&gt;. Добавлю внизу следующий тест:&lt;/p&gt;
  &lt;pre id=&quot;vSMK&quot;&gt;# https://github.com/rakudo/rakudo/issues/1632
{
  my $got;
  sub with-named(:$value) { $got = $value };
  lives-ok { with-named(:value(3 R- 2)) }, &amp;quot;call doesn&amp;#x27;t throw&amp;quot;;
  is $got, -1, &amp;quot;named is good&amp;quot;;
}&lt;/pre&gt;
  &lt;p id=&quot;HCPz&quot;&gt;В тесте есть функция &lt;code&gt;with-named&lt;/code&gt; c именованным параметром. Его значение сохраняется во временную переменную &lt;code&gt;$got&lt;/code&gt; и в конце теста сравнивается с ожидаемым значением. Кроме того, проверяем, не падает ли вызов функции вообще. Запустить отдельный тест для только что собранного компилятора можно с помощью &lt;code&gt;make&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;VDCw&quot;&gt;&amp;gt; make t/spec/S03-metaops/reverse.t
[...]
ok 69 - [R~]=
not ok 70 - Colonpair exists
# Failed test &amp;#x27;Colonpair exists&amp;#x27;
# at t/spec/S03-metaops/reverse.t line 191
# expected: &amp;#x27;\(:foo(-1))&amp;#x27;
#      got: &amp;#x27;\(-1)&amp;#x27;
# You planned 69 tests, but ran 70
# You failed 1 test of 70&lt;/pre&gt;
  &lt;p id=&quot;JAak&quot;&gt;Видно, что тест упал (как и ожидалось). Ещё есть отдельное замечание, что система ожидала 69 тестов, а получила 70. Это особенность тестовой системы, основанной на &lt;a href=&quot;https://en.wikipedia.org/wiki/Test_Anything_Protocol&quot; target=&quot;_blank&quot;&gt;TAP&lt;/a&gt; — нужно поправить число, передаваемое в функцию &lt;code&gt;plan&lt;/code&gt; вверху файла. Теперь тест падает, но на количество не ругается. Можно приступать к исправлениям.&lt;/p&gt;
  &lt;p id=&quot;vN3R&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;9FOV&quot;&gt;Метод пристального взгляда&lt;/h3&gt;
  &lt;p id=&quot;ezwa&quot;&gt;Сначала я доверился меткам на задачке — если это &lt;code&gt;parsing&lt;/code&gt;, значит проблема где-то на стадии разбора исходного кода. Мои знания на данный момент следующие:&lt;/p&gt;
  &lt;ol id=&quot;OCZz&quot;&gt;
    &lt;li id=&quot;mEaz&quot;&gt;Код основного парсера находится в файле &lt;code&gt;&lt;a href=&quot;https://github.com/rakudo/rakudo/blob/3235f3e421e489d1b55f1dda4aaac6b5fc558f4e/src/Perl6/Grammar.nqp&quot; target=&quot;_blank&quot;&gt;rakudo/src/Perl6/Grammar.nqp&lt;/a&gt;&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;lN7O&quot;&gt;Этот парсер наследуется от базового парсера из файла &lt;code&gt;&lt;a href=&quot;https://github.com/Raku/nqp/blob/a301e20ce8bda96375f920430da6564022c778cd/src/HLL/Grammar.nqp&quot; target=&quot;_blank&quot;&gt;nqp/src/HLL/Grammar.nqp&lt;/a&gt;&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;3UL0&quot;&gt;Метаоператоры парсятся и работают похожим образом и можно методом пристального взгляда найти различия.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;m0cm&quot;&gt;В коде основного парсера я нашёл упоминания метаоператоров:&lt;/p&gt;
  &lt;pre id=&quot;fVYV&quot;&gt;token infix_prefix_meta_operator:sym&amp;lt;R&amp;gt; {
  &amp;lt;sym&amp;gt; &amp;lt;infixish(&amp;#x27;R&amp;#x27;)&amp;gt; {}
  &amp;lt;.can_meta($&amp;lt;infixish&amp;gt;, &amp;quot;reverse the args of&amp;quot;)&amp;gt;
  &amp;lt;O=.revO($&amp;lt;infixish&amp;gt;)&amp;gt;
}

token infix_prefix_meta_operator:sym&amp;lt;Z&amp;gt; {
  &amp;lt;sym&amp;gt; &amp;lt;infixish(&amp;#x27;Z&amp;#x27;)&amp;gt; {}
  &amp;lt;.can_meta($&amp;lt;infixish&amp;gt;, &amp;quot;zip with&amp;quot;)&amp;gt;
  &amp;lt;O(|%list_infix)&amp;gt;
}&lt;/pre&gt;
  &lt;p id=&quot;C6i3&quot;&gt;Тут нужны некоторые знания по &lt;a href=&quot;https://docs.raku.org/language/grammar_tutorial&quot; target=&quot;_blank&quot;&gt;грамматикам&lt;/a&gt; в &lt;code&gt;Raku&lt;/code&gt;. Из моих знаний выходило, что принципиальной разницы в парсинге этих двух метаоператоров нет. Через какое-то время, вдоволь накопавшись в исходных кодах парсера, я начал подозревать, что парсинг работает правильно. Мысль, что код&lt;br /&gt;&lt;code&gt;my $r = :foo(3 R- 2); say $r; # Output: foo =&amp;gt; -1&lt;/code&gt;&lt;br /&gt;работает корректно подсказывала — проблема возникает именно при вызове функции. Видимо, я зря доверился метке на задачке.&lt;/p&gt;
  &lt;p id=&quot;ygw6&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;wpfU&quot;&gt;Компилятор нам поможет&lt;/h3&gt;
  &lt;p id=&quot;7Xqn&quot;&gt;Довольно запоздало я вспомнил, что должен был сделать с самого начала. У компилятора &lt;code&gt;Rakudo&lt;/code&gt; есть отладочный ключ &lt;code&gt;--target&lt;/code&gt;. Он принимает название стадии работы компилятора, результат которой нужно вывести на консоль и выйти. Пробую посмотреть на &lt;code&gt;--target=parse&lt;/code&gt; (так как, только о нём и знаю):&lt;/p&gt;
  &lt;blockquote id=&quot;Ts2Y&quot;&gt;Я использую &lt;code&gt;rakumo-m&lt;/code&gt; из папки &lt;code&gt;~/dev-rakudo/rakudo&lt;/code&gt;, чтобы не ждать, пока нужные файлы скопируются в &lt;code&gt;~/dev-rakudo-install&lt;/code&gt; командой &lt;code&gt;make install&lt;/code&gt;. Простые скрипты можно запускать так. Более сложные — придётся запускать из &lt;code&gt;-install&lt;/code&gt; после &lt;code&gt;make install&lt;/code&gt;.&lt;/blockquote&gt;
  &lt;pre id=&quot;qf14&quot;&gt;&amp;gt; cat ~/test.raku
sub s(|c) { say c }
s(:foo(3 R- 2));
s(:foo(3 Z- 2));

&amp;gt; ./rakudo-m --target=parse ~/test.raku
[...]
- args: (:foo(3 R- 2))
  - semiarglist: :foo(3 R- 2)
    - arglist: 1 matches
      - EXPR: :foo(3 R- 2)
        - colonpair: :foo(3 R- 2)
          - identifier: foo
          - coloncircumfix: (3 R- 2)
            - circumfix: (3 R- 2)
              - semilist: 3 R- 2
                - statement: 1 matches
                  - EXPR: R- 2
[...]
- args: (:foo(3 Z- 2))
  - semiarglist: :foo(3 Z- 2)
    - arglist: 1 matches
      - EXPR: :foo(3 Z- 2)
        - colonpair: :foo(3 Z- 2)
          - identifier: foo
          - coloncircumfix: (3 Z- 2)
            - circumfix: (3 Z- 2)
              - semilist: 3 Z- 2
                - statement: 1 matches
                  - EXPR: Z- 2
[...]&lt;/pre&gt;
  &lt;p id=&quot;eUjQ&quot;&gt;Вывод: парсинг проходит одинаково и для &lt;code&gt;R&lt;/code&gt;, и для &lt;code&gt;Z&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;g3Un&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;dUct&quot;&gt;Это был не парсинг&lt;/h3&gt;
  &lt;p id=&quot;fLnS&quot;&gt;Всё, что распарсилось, передаётся в так называемые &lt;a href=&quot;https://docs.raku.org/language/grammar_tutorial#Grammar_actions&quot; target=&quot;_blank&quot;&gt;Actions&lt;/a&gt; для превращения литералов в синтаксическое дерево. В нашем случае &lt;code&gt;Actions&lt;/code&gt; лежат в файлах &lt;code&gt;&lt;a href=&quot;https://github.com/rakudo/rakudo/blob/3235f3e421e489d1b55f1dda4aaac6b5fc558f4e/src/Perl6/Actions.nqp&quot; target=&quot;_blank&quot;&gt;rakudo/src/Perl6/Actions.nqp&lt;/a&gt;&lt;/code&gt; и &lt;code&gt;&lt;a href=&quot;https://github.com/Raku/nqp/blob/a301e20ce8bda96375f920430da6564022c778cd/src/HLL/Actions.nqp&quot; target=&quot;_blank&quot;&gt;nqp/src/HLL/Actions.nqp&lt;/a&gt;&lt;/code&gt;. Тут немного проще разобраться — всё-таки это код, а не грамматика.&lt;/p&gt;
  &lt;p id=&quot;pJ2R&quot;&gt;В основных &lt;code&gt;Actions&lt;/code&gt; я нашёл следующих код:&lt;/p&gt;
  &lt;pre id=&quot;4Da8&quot;&gt;[...]
elsif $&amp;lt;infix_prefix_meta_operator&amp;gt; {
[...]
  if    $metasym eq &amp;#x27;R&amp;#x27; { $helper := &amp;#x27;&amp;amp;METAOP_REVERSE&amp;#x27;; $t := nqp::flip($t) if $t; }
  elsif $metasym eq &amp;#x27;X&amp;#x27; { $helper := &amp;#x27;&amp;amp;METAOP_CROSS&amp;#x27;; $t := nqp::uc($t); }
  elsif $metasym eq &amp;#x27;Z&amp;#x27; { $helper := &amp;#x27;&amp;amp;METAOP_ZIP&amp;#x27;; $t := nqp::uc($t); }
  
  my $metapast := QAST::Op.new( :op&amp;lt;call&amp;gt;, :name($helper), WANTED($basepast,&amp;#x27;infixish&amp;#x27;) );
  $metapast.push(QAST::Var.new(:name(baseop_reduce($base&amp;lt;OPER&amp;gt;&amp;lt;O&amp;gt;.made)), :scope&amp;lt;lexical&amp;gt;))
    if $metasym eq &amp;#x27;X&amp;#x27; || $metasym eq &amp;#x27;Z&amp;#x27;;
[...]&lt;/pre&gt;
  &lt;p id=&quot;WrNY&quot;&gt;Тут говорится, что если в коде распарсился метаоператор &lt;code&gt;R&lt;/code&gt;, &lt;code&gt;Z&lt;/code&gt; или &lt;code&gt;X&lt;/code&gt;, то нужно добавить в синтаксическое дерево вызов неких &lt;code&gt;METAOP_&lt;/code&gt; функций. В случае &lt;code&gt;Z&lt;/code&gt; и &lt;code&gt;X&lt;/code&gt;, у них будет ещё один аргумент — некая &lt;code&gt;reduce&lt;/code&gt; функция. Все эти функции нашлись в &lt;code&gt;&lt;a href=&quot;https://github.com/rakudo/rakudo/blob/3235f3e421e489d1b55f1dda4aaac6b5fc558f4e/src/core.c/metaops.pm6&quot; target=&quot;_blank&quot;&gt;rakudo/src/core.c/metaops.pm6&lt;/a&gt;&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;8DtQ&quot;&gt;sub METAOP_REVERSE(\op) is implementation-detail {
  -&amp;gt; |args { op.(|args.reverse) }
}

sub METAOP_ZIP(\op, &amp;amp;reduce) is implementation-detail {
 nqp::if(op.prec(&amp;#x27;thunky&amp;#x27;).starts-with(&amp;#x27;.&amp;#x27;),
  -&amp;gt; +lol {
    my $arity = lol.elems;
    [...]
  },
  -&amp;gt; +lol {
    Seq.new(Rakudo::Iterator.ZipIterablesOp(lol,op))
  }
  )
}&lt;/pre&gt;
  &lt;p id=&quot;unhv&quot;&gt;Здесь:&lt;/p&gt;
  &lt;ol id=&quot;epXP&quot;&gt;
    &lt;li id=&quot;w5jj&quot;&gt;&lt;code&gt;\op&lt;/code&gt; это операция, перед которой стоит наш метаоператор, то есть &lt;code&gt;-&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;8MGG&quot;&gt;&lt;a href=&quot;https://docs.raku.org/language/traits#is_implementation-detail_trait&quot; target=&quot;_blank&quot;&gt;Trait&lt;/a&gt; &lt;code&gt;implementation-detail&lt;/code&gt; просто указывает на то, что это не публичный код и является частью реализации компилятора;&lt;/li&gt;
    &lt;li id=&quot;zE88&quot;&gt;У операции &lt;code&gt;-&lt;/code&gt; нет характеристики &lt;code&gt;thunky&lt;/code&gt;, значит функция &lt;code&gt;&amp;amp;reduce&lt;/code&gt; не будет участвовать в вычислениях и результатом &lt;code&gt;Z&lt;/code&gt; является &lt;code&gt;Seq.new(...)&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;EizO&quot;&gt;Результатом &lt;code&gt;R&lt;/code&gt; является вызов операции &lt;code&gt;-&lt;/code&gt; с аргументами в обратном порядке.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;WxLl&quot;&gt;В этот момент я вспомнил, что есть ещё один &lt;code&gt;--target&lt;/code&gt;, а именно — &lt;code&gt;ast&lt;/code&gt;. Он покажет результат работы &lt;code&gt;Actions&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;htmZ&quot;&gt;&amp;gt; ./rakudo-m --target=ast ~/test.raku
[...]
- QAST::Op(call &amp;amp;s) &amp;lt;sunk&amp;gt; :statement_id&amp;lt;4&amp;gt; s(:foo(3 R- 2))
  - QAST::Op+{QAST::SpecialArg}(call :named&amp;lt;foo&amp;gt;) &amp;lt;wanted&amp;gt; :statement_id&amp;lt;5&amp;gt; :before_promotion&amp;lt;?&amp;gt; R-
    - QAST::Op(call &amp;amp;METAOP_REVERSE) &amp;lt;wanted&amp;gt; :is_pure&amp;lt;?&amp;gt;
      - QAST::Var(lexical &amp;amp;infix:&amp;lt; - &amp;gt;) &amp;lt;wanted&amp;gt;
    - QAST::Want &amp;lt;wanted&amp;gt; 3
      - QAST::WVal(Int)
      - Ii
      - QAST::IVal(3)  3
    - QAST::Want &amp;lt;wanted&amp;gt; 2
      - QAST::WVal(Int)
      - Ii
      - QAST::IVal(2)  2
[...]
- QAST::Op(call &amp;amp;s) &amp;lt;sunk&amp;gt; :statement_id&amp;lt;7&amp;gt; s(:foo(3 Z- 2))
  - QAST::Op+{QAST::SpecialArg}(:named&amp;lt;foo&amp;gt;) &amp;lt;wanted&amp;gt; :statement_id&amp;lt;8&amp;gt; :before_promotion&amp;lt;?&amp;gt; Z-
    - QAST::Op(call &amp;amp;METAOP_ZIP) &amp;lt;wanted&amp;gt; :is_pure&amp;lt;?&amp;gt;
      - QAST::Var(lexical &amp;amp;infix:&amp;lt; - &amp;gt;) &amp;lt;wanted&amp;gt;
      - QAST::Var(lexical &amp;amp;METAOP_REDUCE_LEFT)
    - QAST::Want &amp;lt;wanted&amp;gt; 3
      - QAST::WVal(Int)
      - Ii
      - QAST::IVal(3)  3
    - QAST::Want &amp;lt;wanted&amp;gt; 2
      - QAST::WVal(Int)
      - Ii
      - QAST::IVal(2)  2
[...]&lt;/pre&gt;
  &lt;p id=&quot;LYim&quot;&gt;Как и ожидалось. Всё почти одинаково, за исключением того, что вызываются разные &lt;code&gt;METAOP_&lt;/code&gt; функции. Как мы знаем из их кода, принципиально эти функции различаются типом возвращаемого значение — &lt;code&gt;Int&lt;/code&gt; и &lt;code&gt;Seq&lt;/code&gt;, соответственно. Известно, что &lt;code&gt;Raku&lt;/code&gt; довольно чувствительный к контекстам, к объектам разных типов… Вдруг дело именно в возвращаемом значении, подумал я. Пробую изменить код следующим образом:&lt;/p&gt;
  &lt;pre id=&quot;7kgQ&quot;&gt;sub METAOP_REVERSE(\op) is implementation-detail {
  -&amp;gt; |args { Seq.new(op.(|args.reverse)) }
}&lt;/pre&gt;
  &lt;p id=&quot;860H&quot;&gt;Компилирую, запускаю:&lt;/p&gt;
  &lt;pre id=&quot;HDRv&quot;&gt;&amp;gt; make
[...]
Stage start      :   0.000
Stage parse      :  61.026
Stage syntaxcheck:   0.000
Stage ast        :   0.000
Stage optimize   :   7.076
Stage mast       :  14.120
Stage mbc        :   3.941
[...]
&amp;gt; ./rakudo-m ~/test.raku
\(-1)
\(:foo((1,).Seq))&lt;/pre&gt;
  &lt;p id=&quot;sGZP&quot;&gt;Ничего не изменилось. Значит, дело не в возвращаемом значении… После некоторых раздумий я насторожился — почему результат опять получился &lt;code&gt;-1&lt;/code&gt;, а не &lt;code&gt;(-1,).Seq&lt;/code&gt;? Более того, судя по коду &lt;code&gt;&lt;a href=&quot;https://github.com/rakudo/rakudo/blob/3235f3e421e489d1b55f1dda4aaac6b5fc558f4e/src/core.c/Seq.pm6&quot; target=&quot;_blank&quot;&gt;Seq&lt;/a&gt;&lt;/code&gt; вообще не имеет подходящего конструктора. Следующая попытка, в качестве бреда — делаю так, чтобы вызов результата &lt;code&gt;METAOP_REVERSE&lt;/code&gt; просто падал:&lt;/p&gt;
  &lt;pre id=&quot;6TwJ&quot;&gt;sub METAOP_REVERSE(\op) is implementation-detail {
  -&amp;gt; |args { die }
}&lt;/pre&gt;
  &lt;p id=&quot;1awk&quot;&gt;Компилирую, запускаю:&lt;/p&gt;
  &lt;pre id=&quot;y5pW&quot;&gt;&amp;gt; make
[...]
&amp;gt; ./rakudo-m ~/test.raku
\(-1)
\(:foo((1,).Seq))&lt;/pre&gt;
  &lt;p id=&quot;B6tX&quot;&gt;Как так?! В синтаксическом дереве присутствует вызов &lt;code&gt;METAOP_REVERSE&lt;/code&gt;, её код должен рухнуть, но вычисления всё равно происходят и получается &lt;code&gt;-1&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;Wlhh&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;RyAW&quot;&gt;Это были не Actions&lt;/h3&gt;
  &lt;p id=&quot;LUG3&quot;&gt;Тут мой взгляд падает на лог сборки компилятора. Там как раз перечислены какие-то &lt;code&gt;Stage&lt;/code&gt;. Наобум пробую &lt;code&gt;--target=mast&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;7f5t&quot;&gt;&amp;gt; ./rakudo-m --target=mast ~/test.raku
[...]
MAST::Frame name&amp;lt;s&amp;gt;, cuuid&amp;lt;1&amp;gt;
  Local types: 0&amp;lt;obj&amp;gt;, 1&amp;lt;obj&amp;gt;, 2&amp;lt;obj&amp;gt;, 3&amp;lt;obj&amp;gt;, 4&amp;lt;int&amp;gt;, 5&amp;lt;str&amp;gt;, 6&amp;lt;obj&amp;gt;, 7&amp;lt;obj&amp;gt;, 8&amp;lt;obj&amp;gt;,
  Lexical types: 0&amp;lt;obj&amp;gt;, 1&amp;lt;obj&amp;gt;, 2&amp;lt;obj&amp;gt;, 3&amp;lt;obj&amp;gt;, 4&amp;lt;obj&amp;gt;,
  Lexical names: 0&amp;lt;c&amp;gt;, 1&amp;lt;$¢&amp;gt;, 2&amp;lt;$!&amp;gt;, 3&amp;lt;$/&amp;gt;, 4&amp;lt;$*DISPATCHER&amp;gt;,
  Lexical map: $!&amp;lt;2&amp;gt;, c&amp;lt;0&amp;gt;, $*DISPATCHER&amp;lt;4&amp;gt;, $¢&amp;lt;1&amp;gt;, $/&amp;lt;3&amp;gt;,
  Outer: name&amp;lt;&amp;lt;unit&amp;gt;&amp;gt;, cuuid&amp;lt;2&amp;gt;
[...]&lt;/pre&gt;
  &lt;p id=&quot;llzE&quot;&gt;Какая-то нечитаемая матрица. Между &lt;code&gt;ast&lt;/code&gt; и &lt;code&gt;mast&lt;/code&gt; есть &lt;code&gt;Stage optimize&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;nlRE&quot;&gt;&amp;gt; ./rakudo-m --target=optimize ~/test.raku
[...]
- QAST::Op(callstatic &amp;amp;s) &amp;lt;sunk&amp;gt; :statement_id&amp;lt;4&amp;gt; s(:foo(3 R- 2))
  - QAST::Op(call &amp;amp;infix:&amp;lt; - &amp;gt;)  :METAOP_opt_result&amp;lt;?&amp;gt;
    - QAST::Want &amp;lt;wanted&amp;gt; 2
      - QAST::WVal(Int)
      - Ii
      - QAST::IVal(2)  2
    - QAST::Want &amp;lt;wanted&amp;gt; 3
      - QAST::WVal(Int)
      - Ii
      - QAST::IVal(3)  3
[...]
- QAST::Op(callstatic &amp;amp;s) &amp;lt;sunk&amp;gt; :statement_id&amp;lt;7&amp;gt; s(:foo(3 Z- 2))
  - QAST::Op+{QAST::SpecialArg}(call :named&amp;lt;foo&amp;gt;) &amp;lt;wanted&amp;gt; :statement_id&amp;lt;8&amp;gt; :before_promotion&amp;lt;?&amp;gt; Z-
    - QAST::Op(callstatic &amp;amp;METAOP_ZIP) &amp;lt;wanted&amp;gt; :is_pure&amp;lt;?&amp;gt;
      - QAST::Var(lexical &amp;amp;infix:&amp;lt; - &amp;gt;) &amp;lt;wanted&amp;gt;
      - QAST::Var(lexical &amp;amp;METAOP_REDUCE_LEFT)
    - QAST::Want &amp;lt;wanted&amp;gt; 3
      - QAST::WVal(Int)
      - Ii
      - QAST::IVal(3)  3
    - QAST::Want &amp;lt;wanted&amp;gt; 2
      - QAST::WVal(Int)
      - Ii
      - QAST::IVal(2)  2
[...]&lt;/pre&gt;
  &lt;p id=&quot;4baH&quot;&gt;Ха! Вот оно. После стадии оптимизации пропала строчка:&lt;br /&gt;&lt;code&gt;- QAST::Op+{QAST::SpecialArg}(call :named&amp;lt;foo&amp;gt;) &amp;lt;wanted&amp;gt; :statement_id&amp;lt;5&amp;gt; :before_promotion&amp;lt;?&amp;gt; R-&lt;br /&gt;&lt;/code&gt;и весь вызов &lt;code&gt;METAOP_REVERSE&lt;/code&gt; заменился на обычную операцию &lt;code&gt;-&lt;br /&gt;&lt;/code&gt;(&lt;code&gt;&amp;amp;infix: &amp;lt; - &amp;gt;&lt;/code&gt;). Значит, проблема где-то в &lt;a href=&quot;https://github.com/rakudo/rakudo/blob/3235f3e421e489d1b55f1dda4aaac6b5fc558f4e/src/Perl6/Optimizer.nqp&quot; target=&quot;_blank&quot;&gt;оптимизаторе&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;FrbT&quot;&gt;Упоминания о &lt;code&gt;&amp;amp;METAOP_REVERSE&lt;/code&gt; есть только в методе &lt;code&gt;&lt;a href=&quot;https://github.com/rakudo/rakudo/blob/3235f3e421e489d1b55f1dda4aaac6b5fc558f4e/src/Perl6/Optimizer.nqp#L2217&quot; target=&quot;_blank&quot;&gt;optimize_nameless_call&lt;/a&gt;&lt;/code&gt;, в которую приходит &lt;code&gt;QAST::Op+{QAST::SpecialArg}(call :named&amp;lt;foo&amp;gt;)&lt;/code&gt;. Видимо, именно эта операция ответственна за формирование именованной пары — имя (параметр &lt;code&gt;named&lt;/code&gt;) у неё уже есть, ей нужно вычислить значение. Поверхностно осмотрев пути исполнения метода &lt;code&gt;optimize_nameless_call&lt;/code&gt;, можно сделать вывод, что нас интересует самый &lt;a href=&quot;https://github.com/rakudo/rakudo/blob/3235f3e421e489d1b55f1dda4aaac6b5fc558f4e/src/Perl6/Optimizer.nqp#L2337&quot; target=&quot;_blank&quot;&gt;последний блок&lt;/a&gt;:&lt;/p&gt;
  &lt;pre id=&quot;ryTD&quot;&gt;[...]
  elsif self.op_eq_core($metaop, &amp;#x27;&amp;amp;METAOP_REVERSE&amp;#x27;) {
    return NQPMu unless nqp::istype($metaop[0], QAST::Var)
      &amp;amp;&amp;amp; nqp::elems($op) == 3;
    return QAST::Op.new(:op&amp;lt;call&amp;gt;, :name($metaop[0].name),
      $op[2], $op[1]).annotate_self: &amp;#x27;METAOP_opt_result&amp;#x27;, 1;
  }
[...]&lt;/pre&gt;
  &lt;p id=&quot;70Bg&quot;&gt;Напомню, что до оптимизации дерево выглядело так:&lt;/p&gt;
  &lt;pre id=&quot;7LRD&quot;&gt;[...]
- QAST::Op(call &amp;amp;s) &amp;lt;sunk&amp;gt; :statement_id&amp;lt;4&amp;gt; s(:foo(3 R- 2))
  - QAST::Op+{QAST::SpecialArg}(call :named&amp;lt;foo&amp;gt;) &amp;lt;wanted&amp;gt; :statement_id&amp;lt;5&amp;gt; :before_promotion&amp;lt;?&amp;gt; R-
    - QAST::Op(call &amp;amp;METAOP_REVERSE) &amp;lt;wanted&amp;gt; :is_pure&amp;lt;?&amp;gt;
      - QAST::Var(lexical &amp;amp;infix:&amp;lt; - &amp;gt;) &amp;lt;wanted&amp;gt;
    - QAST::Want &amp;lt;wanted&amp;gt; 3
    - QAST::Want &amp;lt;wanted&amp;gt; 2&lt;/pre&gt;
  &lt;p id=&quot;yk0p&quot;&gt;А после оптимизации — так:&lt;/p&gt;
  &lt;pre id=&quot;N1Uc&quot;&gt;[...]
- QAST::Op(callstatic &amp;amp;s) &amp;lt;sunk&amp;gt; :statement_id&amp;lt;4&amp;gt; s(:foo(3 R- 2))
  - QAST::Op(call &amp;amp;infix:&amp;lt; - &amp;gt;)  :METAOP_opt_result&amp;lt;?&amp;gt;
    - QAST::Want &amp;lt;wanted&amp;gt; 2
    - QAST::Want &amp;lt;wanted&amp;gt; 3
[...]&lt;/pre&gt;
  &lt;p id=&quot;IHsk&quot;&gt;То есть, &lt;code&gt;optimize_nameless_call&lt;/code&gt; делает следующее:&lt;/p&gt;
  &lt;ol id=&quot;gxwC&quot;&gt;
    &lt;li id=&quot;ZBMN&quot;&gt;Если у нашей операции &lt;code&gt;QAST::Op+{QAST::SpecialArg}&lt;/code&gt; не три аргумента, а у вызова &lt;code&gt;METAOP_REVERSE&lt;/code&gt; — первый не нужного типа, то возвращаем пусто. Это не наш случай;&lt;/li&gt;
    &lt;li id=&quot;8YUF&quot;&gt;Иначе, вместо нашей операции &lt;code&gt;QAST::Op+{QAST::SpecialArg}&lt;/code&gt; возвращаем новую, которая вызовет &lt;code&gt;&amp;amp;infix:&amp;lt; - &amp;gt;&lt;/code&gt; аргументами в обратном порядке. То есть, упаковка результата в пару исчезла.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;885T&quot;&gt;Немного поигравшись с тем, как это можно поправить и почитав реализации &lt;code&gt;&lt;a href=&quot;https://github.com/Raku/nqp/blob/a301e20ce8bda96375f920430da6564022c778cd/src/QAST/SpecialArg.nqp&quot; target=&quot;_blank&quot;&gt;QAST::SpecialArg&lt;/a&gt;&lt;/code&gt; и &lt;code&gt;&lt;a href=&quot;https://github.com/Raku/nqp/blob/a301e20ce8bda96375f920430da6564022c778cd/src/QAST/Node.nqp&quot; target=&quot;_blank&quot;&gt;QAST::Node&lt;/a&gt;&lt;/code&gt;, я пришёл к следующему коду:&lt;/p&gt;
  &lt;pre id=&quot;30Su&quot;&gt;[...]
  elsif self.op_eq_core($metaop, &amp;#x27;&amp;amp;METAOP_REVERSE&amp;#x27;) {
    return NQPMu unless nqp::istype($metaop[0], QAST::Var)
      &amp;amp;&amp;amp; nqp::elems($op) == 3;
    my $opt_result := QAST::Op.new(:op&amp;lt;call&amp;gt;, :name($metaop[0].name),
      $op[2], $op[1]).annotate_self: &amp;#x27;METAOP_opt_result&amp;#x27;, 1;
    if $op.named { $opt_result.named($op.named) } # добавить параметр named 
    if $op.flat { $opt_result.flat($op.flat) }    # добавить параметр flat
    return $opt_result;
  }
[...]&lt;/pre&gt;
  &lt;p id=&quot;7Rdr&quot;&gt;И дереву:&lt;/p&gt;
  &lt;pre id=&quot;1kai&quot;&gt;[...]
- QAST::Op(callstatic &amp;amp;s) &amp;lt;sunk&amp;gt; :statement_id&amp;lt;4&amp;gt; s(:foo(3 R- 2))
  - QAST::Op+{QAST::SpecialArg}(call &amp;amp;infix:&amp;lt; - &amp;gt; :named&amp;lt;foo&amp;gt;)  :METAOP_opt_result&amp;lt;?&amp;gt;
    - QAST::Want &amp;lt;wanted&amp;gt; 2
    - QAST::Want &amp;lt;wanted&amp;gt; 3
[...]&lt;/pre&gt;
  &lt;p id=&quot;4cag&quot;&gt;Параметр &lt;code&gt;named&lt;/code&gt; вернулся на место. Тест тоже начал проходить:&lt;/p&gt;
  &lt;pre id=&quot;yecK&quot;&gt;&amp;gt; make t/spec/S03-metaops/reverse.t
[...]
All tests successful.
Files=1, Tests=70,  3 wallclock secs ( 0.03 usr  0.01 sys +  3.61 cusr  0.17 csys =  3.82 CPU)
Result: PASS&lt;/pre&gt;
  &lt;p id=&quot;0TTw&quot;&gt;На этом можно было бы остановиться, но это же код оптимизатора компилятора, а в итоге его работы получился вызов метода &lt;code&gt;-&lt;/code&gt; с двумя целочисленными аргументами. Как-то это неоптимально, на мой взгляд. Если изменить возвращаемое выражение на &lt;code&gt;return self.visit_op: $opt_result;&lt;/code&gt;, чтобы вызвать оптимизатор на получившейся неоптимальной операции, то итоговое дерево будет выглядеть так:&lt;/p&gt;
  &lt;pre id=&quot;G8by&quot;&gt;[...]
- QAST::Op(callstatic &amp;amp;s) &amp;lt;sunk&amp;gt; :statement_id&amp;lt;4&amp;gt; s(:foo(3 R- 2))
  - QAST::Want+{QAST::SpecialArg}(:named&amp;lt;foo&amp;gt;)
    - QAST::WVal+{QAST::SpecialArg}(Int :named&amp;lt;foo&amp;gt;)
    - QAST::IVal(-1)
[...]&lt;/pre&gt;
  &lt;p id=&quot;qJJK&quot;&gt;Теперь всё оптимально.&lt;/p&gt;
  &lt;p id=&quot;Wt80&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;i9nH&quot;&gt;Делимся результатами&lt;/h3&gt;
  &lt;p id=&quot;V2kG&quot;&gt;Мы на финишной прямой. Теперь дело за малым — поделиться наработками:&lt;/p&gt;
  &lt;ol id=&quot;84ZF&quot;&gt;
    &lt;li id=&quot;z2Rv&quot;&gt;ВАЖНО: Прогнать все тесты &lt;code&gt;make spectest&lt;/code&gt; и убедиться, что ничего нового не падает;&lt;/li&gt;
    &lt;li id=&quot;0tqW&quot;&gt;Сделать fork репозиториев с компилятором &lt;code&gt;Rakudo&lt;/code&gt; и тестами на &lt;code&gt;GitHub&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;MmVB&quot;&gt;Добавить fork-репозитории как новые &lt;code&gt;git remote&lt;/code&gt;:&lt;br /&gt;&lt;code&gt;cd ~/dev-rakudo/rakudo &amp;amp;&amp;amp; git remote add fork &amp;lt;url-of-your-rakudo-fork&amp;gt;&lt;br /&gt;cd ~/dev-rakudo/rakudo/t/spec &amp;amp;&amp;amp; git remote add fork &amp;lt;url-of-your-roast-fork&amp;gt;&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;VGAO&quot;&gt;ВАЖНО: Убедиться, что в обоих репозиториях правильно выставлены &lt;code&gt;user.name&lt;/code&gt; и &lt;code&gt;user.email&lt;/code&gt; в &lt;code&gt;git&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;pJHE&quot;&gt;Сделать коммиты в оба репозитория с подробным описание зачем и какие были сделаны изменения, добавить ссылки на оригинальную задачку трекере;&lt;/li&gt;
    &lt;li id=&quot;Z3B1&quot;&gt;Запушить коммиты:&lt;br /&gt;&lt;code&gt;cd ~/dev-rakudo/rakudo &amp;amp;&amp;amp; git push fork&lt;br /&gt;cd ~/dev-rakudo/rakudo/t/spec &amp;amp;&amp;amp; git push fork&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;A5US&quot;&gt;Сделать pull request в оба репозитория. В описаниях к ним лучше добавить перекрёстные ссылки друг на друга и на оригинальную задачку.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;RiTo&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;R8qS&quot;&gt;Заключение&lt;/h3&gt;
  &lt;p id=&quot;2jWZ&quot;&gt;Контрибутить в открытое ПО это:&lt;/p&gt;
  &lt;ul id=&quot;Nj1q&quot;&gt;
    &lt;li id=&quot;T5OZ&quot;&gt;весело и интересно;&lt;/li&gt;
    &lt;li id=&quot;crI0&quot;&gt;даёт чувство, что ты делаешь что-то полезное, и это действительно так;&lt;/li&gt;
    &lt;li id=&quot;ksAS&quot;&gt;позволяет познакомиться с новыми интересными и профессиональными людьми (на любые вопросы относительно &lt;code&gt;Raku&lt;/code&gt; вам ответят в &lt;a href=&quot;https://kiwiirc.com/client/irc.libera.chat/#raku&quot; target=&quot;_blank&quot;&gt;IRC канале &lt;code&gt;#raku&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
    &lt;li id=&quot;Pdu1&quot;&gt;замечательный опыт решения нестандартных задач без стресса в виде дедлайна.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;s8BP&quot;&gt;Выбирайте класс героя, который вам сейчас более близок и вперёд навстречу новым квестам!&lt;/p&gt;
  &lt;blockquote id=&quot;2Wy4&quot; data-align=&quot;center&quot;&gt;&lt;a href=&quot;https://translate.google.com/translate?hl=&amp;sl=ru&amp;tl=en&amp;u=rakurs.atroxaper.net%2F2021-02-13-contributing-raku&quot; target=&quot;_blank&quot;&gt;English version&lt;/a&gt;&lt;/blockquote&gt;
  &lt;tt-tags id=&quot;ssXF&quot;&gt;
    &lt;tt-tag name=&quot;rakulang&quot;&gt;#rakulang&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;удачныйракурс&quot;&gt;#удачныйракурс&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;opensource&quot;&gt;#opensource&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;mmorpg&quot;&gt;#mmorpg&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;

</content></entry><entry><id>goodrakurs:2020-06-29-string-priority</id><link rel="alternate" type="text/html" href="https://rakurs.atroxaper.net/2020-06-29-string-priority?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=goodrakurs"></link><title>Когда полезны строковые веса | When string weights are useful</title><published>2021-11-26T19:27:49.905Z</published><updated>2021-11-27T16:30:53.369Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/82/7e/827eb06a-d42f-402b-a2f8-969c63ebb923.jpeg"></media:thumbnail><tt:hashtag>rakulang</tt:hashtag><tt:hashtag>удачныйракурс</tt:hashtag><tt:hashtag>алгоритмы</tt:hashtag><tt:hashtag>сортировка</tt:hashtag><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/31/70/31705cec-641d-4d05-86e2-f54c0c815ca6.jpeg&quot;&gt;Часто бывают случаи, когда нам нужно задать порядок на множестве объектов, у которых нет имманентного им приоритета. Например, список покупок — у абстрактного молока нет какой-то характеристики, по которой мы можем судить, что оно менее приоритетно хлеба. В таком случае объекты наделяются приоритетом снаружи, то есть, он условный. Обычно, программисты назначают тривиальный целочисленный приоритет: молоко — 1, хлеб — 2 и так далее.</summary><content type="html">
  &lt;figure id=&quot;8nAE&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/31/70/31705cec-641d-4d05-86e2-f54c0c815ca6.jpeg&quot; width=&quot;1198&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;nkZP&quot;&gt;Часто бывают случаи, когда нам нужно задать порядок на множестве объектов, у которых нет имманентного им приоритета. Например, список покупок — у абстрактного молока нет какой-то характеристики, по которой мы можем судить, что оно менее приоритетно хлеба. В таком случае объекты наделяются приоритетом снаружи, то есть, он условный. Обычно, программисты назначают тривиальный целочисленный приоритет: молоко — 1, хлеб — 2 и так далее.&lt;/p&gt;
  &lt;blockquote id=&quot;0Faf&quot;&gt;Не стоит путать приоритет (вес) объекта с его идентификатором, который может быть инкрементальным целочисленным или, например, случайным UUID. &lt;/blockquote&gt;
  &lt;p id=&quot;nBHr&quot;&gt;Проблема в том, что подобные множества объектов имеют свойство приобретать новые элементы, и это может доставить неудобства. Представим, что эти объекты где-то хранятся: в хешмапе (и по какой-то нелепой случайности, поле приоритета используется в вычислении хеша) или, самое неприятное, в базе данных. В какой-то момент обязательно появляется колбаса, которая чуть менее приоритетна хлеба, но более молока.&lt;/p&gt;
  &lt;p id=&quot;wALd&quot;&gt;Ситуация стандартная, поведение тоже — переназначаем новые приоритеты: молоко-1, колбаса — 2, хлеб — 3. Такое переназначение может быть достаточно трудоёмким.&lt;/p&gt;
  &lt;blockquote id=&quot;qLVb&quot;&gt;Часто встречаются предусмотрительные программисты, которые ожидая новых элементов, назначают приоритеты с запасом: молоко — 1, колбаса- 5, хлеб — 10. Это решение не очень элегантное, и запаса может не всегда хватить.&lt;/blockquote&gt;
  &lt;p id=&quot;m1t5&quot;&gt;Другое стандартное поведение — ввести рациональные приоритеты: молоко — 1, колбаса — 1.5, хлеб — 2. Плюс такого подхода в том, что между двумя любыми элементами можно вставить сколько угодно новых. Минусы тоже есть — если заранее не подумать о рациональных приоритетах, то придётся менять тип данных столбца в базе. Ещё один плохой момент — нецелое число представляется в компьютере с округлением, что может привести к неточным расчётам.&lt;/p&gt;
  &lt;p id=&quot;vlKb&quot;&gt;Кстати, почему между любыми двумя рациональными числами можно вставить ещё одно? Потому что, дробная часть числа сравнивается не в числовом порядке (101&amp;gt;11), а в лексикографическом (0.101&amp;lt;0.11). Таким образом, если перейти от чисел к строкам, которые сравниваются лексикографически, то можно избавиться от проблем с возможным округлением. &lt;/p&gt;
  &lt;blockquote id=&quot;muHg&quot;&gt;Слово А меньше B в лексикографическом смысле, если либо А целиком является префиксом В, либо первый слева неравный символ у А меньше, чем у В.&lt;/blockquote&gt;
  &lt;p id=&quot;HYHg&quot;&gt;Вторая часть определения даёт нам возможность сделать так, что между любыми двумя нашими словами можно было вставить третье. Для этого, последний символ любого слова должен быть таким, чтобы в алфавите присутствовали значения и больше, и меньше его. Рассмотрим небольшой алфавит из трёх элементов: &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt; и &lt;code&gt;&amp;gt;&lt;/code&gt;. В таком алфавите &lt;code&gt;=&lt;/code&gt; как раз сгодится на роль последнего символа любого слова.&lt;/p&gt;
  &lt;p id=&quot;6WFO&quot;&gt;Представим, что у нас есть три объекта. Дадим им приоритеты:&lt;/p&gt;
  &lt;figure&gt;
    &lt;script src=&quot;https://gist.github.com/atroxaper/a3e9945f6ea0de924a62e5c839516591.js&quot;&gt;&lt;/script&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hiD9&quot;&gt;Здесь, первая буква добавлена и для наглядности, и для практического удобства — например, это три разные категории товаров: мясное, мучное и молочное. Теперь начинаем добавлять новые объекты: менее приоритетная, чем хлеб булка — &lt;em&gt;b&amp;lt;=&lt;/em&gt; (уменьшили последний символ, он получился крайний в алфавите, следовательно, добавили равно, чтобы не нарушать правило составления слов). Далее, пирожок между булкой и хлебом: берём хлеб, вычисляем приоритет меньший, получаем &lt;em&gt;b&amp;lt;=&lt;/em&gt;, но такой уже присвоен булке, тогда вычисляем приоритет больше нее — &lt;em&gt;b&amp;lt;&amp;gt;=&lt;/em&gt;. То есть алгоритм следующий: нужно вычислить приоритет больше, чем исходный — увеличиваем последний символ и добавляем средний символ алфавита, если увеличеный был крайним. Если полученный приоритет уже существует, то вычисляем приоритет меньший этого и так далее. Алгоритм для вычисления приоритета меньшего исходного аналогичен. Получаем такую картину:&lt;/p&gt;
  &lt;figure&gt;
    &lt;script src=&quot;https://gist.github.com/atroxaper/e7c5cd4e76a24c95b6794bb5d37bae74.js&quot;&gt;&lt;/script&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4VFo&quot;&gt;Минусы этого подхода очевидны — чуть больше памяти на хранение и времени на сравнение. Минусы эти исключительно аппаратного рода, но есть мнение, что преждевременная оптимизация это корень всех зол, а удобство разработчика — благо.&lt;/p&gt;
  &lt;p id=&quot;mBj1&quot;&gt;На языке &lt;code&gt;Raku&lt;/code&gt; такую генерацию можно написать следующим образом:&lt;/p&gt;
  &lt;figure&gt;
    &lt;script src=&quot;https://gist.github.com/atroxaper/14e960b8973d58809e5c38aa4dd90ceb.js&quot;&gt;&lt;/script&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Mzmv&quot;&gt;Объявляется функция &lt;code&gt;next&lt;/code&gt; с тремя аргументами: изначальным словом &lt;code&gt;$w&lt;/code&gt; и сетом уже имеющихся &lt;code&gt;$set&lt;/code&gt; типа &lt;code&gt;Set&lt;/code&gt; и параметром-ключом &lt;code&gt;$prev&lt;/code&gt;, который используется, чтобы генерировать не следующее значение, а предыдущее (см. строку 12). Самые интересные строки это 4 и 5. Тут используется оператор замены &lt;code&gt;s///&lt;/code&gt;. Берется изначальная &lt;code&gt;$w&lt;/code&gt;, в ней ищется подстрока, которая удовлетворяет регулярному выражению между первыми косыми чертами (&lt;code&gt;.$&lt;/code&gt; — один символ перед концом текста) и заменяется за строку, между вторыми косыми чертами (вместо неё мы используем вычислимый блок, в котором берем нужную замену из массива). Стоит заменить, что оператор меняет исходную строку &lt;code&gt;$w&lt;/code&gt;, вместо возвращения новой. Это позволяет использовать переменную &lt;code&gt;$w&lt;/code&gt; в условии цикла в строке 5 (проверка, присутствует ли уже новое слово). Строки с 9 по 12 иллюстрируют использование функции.&lt;/p&gt;
  &lt;blockquote id=&quot;hljC&quot; data-align=&quot;center&quot;&gt;&lt;a href=&quot;https://translate.google.com/translate?hl=&amp;sl=ru&amp;tl=en&amp;u=rakurs.atroxaper.net%2F2020-06-29-string-priority&quot; target=&quot;_blank&quot;&gt;English version&lt;/a&gt;&lt;/blockquote&gt;
  &lt;tt-tags id=&quot;EzOE&quot;&gt;
    &lt;tt-tag name=&quot;rakulang&quot;&gt;#rakulang&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;удачныйракурс&quot;&gt;#удачныйракурс&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;алгоритмы&quot;&gt;#алгоритмы&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;сортировка&quot;&gt;#сортировка&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;

</content></entry></feed>