Разрешить методу работать со списком моего типа?

Raku упрощает поддержку существующих функций в моих новых типах, реализуя методы [multi?|sub?] в моем типе. Однако мне интересно, предоставляет ли он также способ применения существующих (или новых) методов к спискам или другим позиционным коллекциям моего типа (без увеличения списка, что является первым шагом на пути к безумию…).

Чтобы быть более конкретным, вот о чем я говорю, используя класс Point, который, кажется, является всем примером для такого рода вещей:

class Point { 
    has $.x; has $.y; 
    method sum(Point $p) { Point.new: :x($!x + $p.x) :y($!y + $p.y) }
}

my $p1 = Point.new: :8x :3y;

my $p2 = Point.new: :1x :9y;

$p1.sum: $p2; # this works     # OUTPUT: «Point.new(x => 9, y => 12)»

($p1, $p2).sum;  # This is what I want to be able to do

(Я знаю, что в конкретном случае .sum было бы решение, включающее реализацию метода принуждения Numeric для Point, но меня интересует более общее решение.)

Есть ли хороший/чистый способ сделать это? Или я просто использую сокращение и аналогичные функции для суммирования баллов внутри списка, не имея возможности использовать метод .sum в этом списке?


person codesections    schedule 02.04.2021    source источник


Ответы (2)


В ответ на ваш вопрос я изобрел то, что назову четырехэтапным перенаправлением. Это включает четыре шага:

  1. Образец соответствует составным структурам данных, над которыми нужно иметь контроль;

  2. Приведение совпадающих структур данных к новому типу;

  3. Образец соответствует последующим подпрограммам, вызываемым для нового типа;

  4. Перенаправьте совпадающие подпрограммы по желанию.


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

role Point {                                              # <-- Make `role`
    has $.x; has $.y; 
    multi method sum(Point $p) { Point.new: :x($!x + $p.x) :y($!y + $p.y) }

    multi method COERCE ($_) { $_ but Point }             # <-- Hook `sum`
    multi method sum { self.List[0].sum: self.List[1] }   # <-- Reroute `sum`
}

my $p1 = Point.new: :8x :3y;
my $p2 = Point.new: :1x :9y;

say Point($p1, $p2).sum; # OUTPUT: «Point.new(x => 9, y => 12)»

Давайте вспомним четыре шага перемаршрутизации, с которых я начал, и то, как я их реализовал выше:

  1. Образец соответствует составным структурам данных, над которыми нужно иметь контроль;

    В say Point($p1, $p2).sum; я вставил вызов Point(...). Поскольку Point — это тип, это запускает протокол принуждения Раку. Это один из способов начать процесс сопоставления с образцом. Одной частью протокола является вызов метода COERCE типа в некоторых сценариях. Итак, я добавил к вашему типу метод multi method COERCE..., и он вызывается. В этом случае фактический аспект соответствия шаблону этого вызова минимален — всего $_ — но я немного уточню его в другой версии этого решения ниже.

  2. Приведение совпадающих структур данных к новому типу;

    Это код $_ but Point. Смешивание в Point является минимальным, но достаточным принуждением. (Обратите внимание, что это может быть написано наоборот — Point but $_, и оно все равно будет работать.) Чтобы это работало, я изменил декларатор типа для Point с class на role.

  3. Образец соответствует последующим подпрограммам, вызываемым для нового типа;

    Это объявление multi method sum.

  4. Перенаправьте совпадающие подпрограммы по желанию.

    Это код self.List[0].sum: self.List[1].


В качестве иллюстрации более конкретного сопоставления шаблонов на шаге 1:

    multi method COERCE (List $_ (Point, Point)) { $_ but Point }

Если бы вы использовали эту 4-ступенчатую перемаршрутизацию, то шаблоны сигнатур, подобные приведенным выше, вероятно, были бы очень разумными.


Идиома для облегчения более лаконичного кода для шага 4:

    multi method sum ($_: ;; $/ = .List) { $0.sum: $1 }

Это выглядит невероятно безобразно. И для чего? Почему я включил его? Ну, я надеюсь, вы видите потенциал, на который он намекает. Это действительно не имеет ничего общего с вашим вопросом или моим ответом. Но я не следую рациональным правилам в отношении того, когда я собираюсь чем-то поделиться. Я надеюсь, что смогу очаровать, в частности, вас, @codesections, и всех, кто читает это, кто может читать мои мысли.

Во-первых, к сожалению, большинство людей не осознают, что Captures являются важной и малоиспользуемой инновацией в области эргономики в Raku. Capture предназначен не только для записи аргументов. Это не просто родительский класс Match. Это общий тип вложенной структуры данных.

Во-вторых, аналогично, $/ — это не просто текущая переменная Match. Это также очень удобная функция автодеструкции для Captures, то есть автоматическая деструктуризация общих вложенных структур данных.

Я обдумывал, как лучше всего ввести в культуру раку следующее, но я считаю, что было бы здорово иметь соответствующие элегантные / практичные / нюансированные функции, соответствующие трем переменным пунктуации $_, $/ и $!.

Я назвал свое соломенное предложение (в уме) «Данные», хорошо? Обратите внимание, как это три слова. За последние несколько лет я обработал массу деталей, но я надеюсь, что вы сможете интуитивно представить, куда мы могли бы отправиться, если бы пошли по пути, который я предлагаю. Я действительно должен написать это вкратце; это странное приложение к этому ответу SO является авансовым платежом.

person raiph    schedule 03.04.2021
comment
Это интересный подход, о котором я и не подумал — хотя я знаю, что , создает список, мне все же пришлось минуту смотреть на этот код, прежде чем я понял, что он говорит то же самое. как Point.COERCE(List(1, 2)) вместо вызова &Point с двумя аргументами. С другой стороны, код в том виде, в каком он написан, на самом деле у меня не работает; по крайней мере, на Rakudo 2021.03 я получаю ошибку none of these signatures match при запуске этого кода. - person codesections; 03.04.2021
comment
Я устал, и мне нужно было еще что-то сказать, но я хотел опубликовать то, что у меня было. А то строчку пропустил! Я исправил это и написал ответ, более похожий на то, что я намеревался. И теперь есть бонусное приложение It's Data, OK?... - person raiph; 04.04.2021

Вы можете добавить выделенного кандидата infix:<+>, а затем использовать метаоперацию [+]:

multi sub infix:<+>(Point:D \a, Point:D \b) { 
    a.sum(b)
}

say [+] $p1,$p2;  # Point.new(x => 9, y => 12)

Метод List.sum действительно использует infix:<+> внутри, но, к сожалению, сначала он пытается вызвать Numeric для него. Это можно считать ошибкой: мы рассмотрим это.

person Elizabeth Mattijsen    schedule 02.04.2021
comment
Метод sum в CORE.setting не будет иметь кандидата на мультидиспетчеризацию Point в лексической области, поэтому он не действует. Это довольно неприятный побочный эффект от [+] вызова .sum в качестве особого случая. - person Jonathan Worthington; 03.04.2021
comment
Но как ни странно, [+] $p1, $p2 работает, даже если внутри вызывает .sum? - person Elizabeth Mattijsen; 03.04.2021
comment
Спасибо, это полезно знать — и это очень соответствует духу того, что я имел в виду, говоря, что простое использование редукции было бы хорошей второй лучшей альтернативой возможности вызова метода в самом списке. - person codesections; 03.04.2021
comment
@ElizabethMattijsen [+] вызывает только внутренние вызовы .sum, если infix:<+> не был изменен. Если вы добавите множественный кандидат, он не будет вызывать .sum, вместо этого он будет делать это медленным способом. - person Brad Gilbert; 05.04.2021
comment
Ах, да, и угадайте, кто сделал эту оптимизацию... :-) - person Elizabeth Mattijsen; 06.04.2021