Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing a method to operate on a List of my type?

Raku makes it very easy to support existing functions on my new types by implementing [multi?|sub?] methods on my type. However, I'm wondering if it also provides a way to have existing (or new) methods apply to Lists or other positional collections of my type (without augmenting List, which is the first step on the path to madness…).

To be more concrete, here's what I'm talking about, using the Point class that seems to be everyone's go-to example for this sort of thing:

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

(I know that, in the specific case of .sum, there'd be a solution involving implementing a Numeric coercion method on Point, but I'm interested in a more general solution.)

Is there a good/clean way to do this? Or do I just use reduction and similar features to sum Points inside a List without being able to use a .sum method on that List?

like image 223
codesections Avatar asked Apr 02 '21 21:04

codesections


1 Answers

I've invented what I'll call "4 step rerouting" in response to your question. This has four steps:

  1. Pattern match composite data structures one wants control over;

  2. Coerce matched data structures to a new type;

  3. Pattern match subsequent routines called on the new type;

  4. Reroute the matched routines as desired.


I've played with diverse ways to implement this 4 step rerouting, some using OO, others just functions. The following is about the shortest path to a solution that I've come up with for your particular example:

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)»

Let's recap the four rerouting steps I began with and how I've implemented them above:

  1. Pattern match composite data structures one wants control over;

    In say Point($p1, $p2).sum; I've inserted a Point(...) call. Because Point is a type this triggers Raku's coercion protocol. This is one way to start a process of pattern matching. One part of the protocol is to call the type's COERCE method in some scenarios. So I've added a multi method COERCE... method to your type, and it gets called. In this instance the actual "pattern match" aspect of this call is minimal -- just $_ -- but I'll elaborate it a little in another version of this solution below.

  2. Coerce matched data structures to a new type;

    This is the $_ but Point code. Mixing in Point is minimal but sufficient coercion. (Note that it could be written the other way around -- Point but $_ and it'll still work.) To make this work I switched the type declarator for Point from class to role.

  3. Pattern match subsequent routines called on the new type;

    This is the multi method sum declaration.

  4. Reroute the matched routines as desired.

    This the self.List[0].sum: self.List[1] code.


As an illustration of more specific pattern matching in step 1:

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

If you were to use this 4 step rerouting then signature patterns like the above are probably very sensible.


An idiom to facilitate more succinct code for step 4:

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

This looks incredibly ugly. And for what? Why have I included it? Well, I'm hoping you can see the potential it hints at. It really has nothing to do with your question or my answer. But I don't follow rational rules about when I'm going to share something. I'm hoping I might be able to charm you in particular, @codesections, and anyone else reading this who can read my mind.

First, afaict, most folk don't realize that Captures are an important underused ergonomics innovation in Raku. Capture is not just for capturing arguments. It's not just the parent class of Match. It's a generic "nested data structure" type.

Second, along similar lines, $/ is not just the current Match variable. It's also a really convenient auto-destructure for Captures, which is to say auto-destructure for generic nested data structures.

I've been mulling how the following might best be introduced into Raku culture but I'm of the opinion that it would be great to have appropriate elegant/practical/nuanced features corresponding to the three punctuation variables $_, $/, and $!.

I've named my strawman proposal (in my head) "It's Data, OK?". Note how it's three words. There's a ton of details I've worked out in my head about this in the last few years but I'm hopeful you can intuitively begin to imagine where we could head if we went down the path I suggest. I really should write it up in a gist; this odd appendix of this SO answer is a down-payment.

like image 83
raiph Avatar answered Sep 18 '22 23:09

raiph