Suppose we have the following class composing the role Iterable:
class Word-Char does Iterable {
has #.words;
method !pairize($item) {
return $item => $item.chars;
}
method iterator( Word-Char:D: ) {
#!words.map({self!pairize($_)}).rotor(1).iterator
}
}
I could assign the object to a Positional variable during object construction and iterate over that variable:
my #words = Word-Char.new: words => <the sky is blue>;
.say for #words;
OUTPUT:
(the => 3)
(sky => 3)
(is => 2)
(blue => 4)
However, what if the object is being passed around? How do I make sure it's still iterable?:
my $w = Word-Char.new: words => <the sky is blue>;
sub f( $w ) {
.say for $w
}
f($w);
OUTPUT:
Word-Char.new(words => ["the", "sky", "is", "blue"])
Goal:
By using Iterable, Iterator or both, I would like, if possible, to be able to iterate over an instance object of the class implementing these roles anywhere. Right now I know that by assigning the instance object during the object construction to a Positional variable, I can get the iterable items the class provide but this isn't what I want. Instead I want to pass the object itself and iterate over it wherever/whenever I deem it necessary.
When dealing with scalar values that do the iterator role, the simplest way to accomplish what you are attempting is to tell perl6 your scalar value is iterable. You can do that by postfixing it with []. Your example then looks like this:
my $w = Word-Char.new: words => <the sky is blue>;
.say for $w[]
Another thing....
Your iteration code has a bug in that it doesn't reset itself before returning IterationEnd. A quick fix looks like the following:
class Word-Char does Iterable does Iterator {
has #.words;
has Int $!index = 0;
method !pairize($item) {
return $item => $item.chars;
}
method iterator() {self}
method pull-one( --> Mu ) {
if $!index < #!words.elems {
my $item = #!words[$!index];
$!index += 1;
return self!pairize($item);
}
else {
$!index = 0;
return IterationEnd;
}
}
}
However, this means that you have to keep all of the iteration logic (and its attributes) with the main class. Another, way would be to use an anonymous class, instead of using self:
class Word-Char does Iterable {
has #.words;
method !pairize($item) {
return $item => $item.chars;
}
method iterator() {
my #words = #!words;
class :: does Iterator {
has $.index is rw = 0;
method pull-one {
return IterationEnd if $!index >= #words.elems;
#words[$!index++];
}
}.new;
}
}
The advantage of the above is that you can keep your iteration logic cleaner and isolated from the rest of the object. You also don't need to worry about resetting state.
OK, not clear what you want to achieve here, but let's give it a try.
The main problem in the second example is that you have changed a Positional (with w) with an Scalar. Simply use again #w and you're set
my #w = Word-Char.new: words => <the sky is blue>;
sub f( #w ) {
.say for #w
}
f(#w);
This would work exactly in the same way, because #w is still Positional, and thus Iterable. When you call $w, the Scalar just returns its only item, which is the object, and that's what is printed. If you want to use the scalar sigil on this object and also iterate over it, you need to make it an Iterator also.
On #perl6, jnthn provided several approaches. Some of them don't behave as I expect them to though.
I updated the class as follow as per
jjmerelo's suggestion:
class Word-Char does Iterable does Iterator {
has #.words;
has Int $!index = 0;
method !pairize($item) {
return $item => $item.chars;
}
method iterator() {self}
method pull-one( --> Mu ) {
if $!index < #!words.elems {
my $item = #!words[$!index];
$!index += 1;
return self!pairize($item);
}
else {
return IterationEnd;
}
}
}
1. Bind the object to a Positional
# Binding to a Positional
my #w01 := Word-Char.new: words => <the sky is blue>;
This produces the following error:
Type check failed in binding; expected Positional but got Word-Char...
2. Use | at the point of iteration
my $w = Word-Char.new: words => <the sky is blue>;
for |$w {
.say
}
=begin comment
Word-Char.new(words => ["the", "sky", "is", "blue"])
=end comment
| doesn't have an affect on the object which seems to hold on to its scalar nature and thus for doesn't iterate over it.
3. Use a sigilless variable
my \w = Word-Char.new: words => <the sky is blue>;
for w {
.say
}
=begin comment
he => 3
sky => 3
is => 2
blue => 4
=end comment
So far this is the cleanest approach which does what I expect.
4. Rather than making the class iterable, add a method that returns something iterable.
In fact, this one was my first approach but I didn't find it to be too p6y. In any case, for this to work we need to update our class and add a method that returns something iterable. The method's name of my choice is LOOP-OVER if only to make it stand out from everything else.
class Word-Char {
has #.words;
method !pairize($item) {
return $item => $item.chars;
}
method LOOP-OVER {
gather for #!words -> $word {
take self!pairize($word)
}
}
}
my $w = Word-Char.new: words => <the sky is blue>;
for $w.LOOP-OVER {
.say
}
=begin comment
he => 3
sky => 3
is => 2
blue => 4
=end comment
But what if we rely on several classes behaving iteratively? How do we make sure they implement the same method? The most straightforward way
is to compose a role (e.g., Iterationable) which implements a stub LOOP-OVER method, in this instance.
role Iterationable {
method LOOP-OVER { ... }
}
class Word-Char does Iterationable {
has #.words;
method !pairize($item) {
return $item => $item.chars;
}
method LOOP-OVER {
gather for #!words -> $word {
take self!pairize($word)
}
}
}
class Names does Iterationable {
has #.names;
method LOOP-OVER {
gather for #!names -> $name {
take $name.split(/\s+/)».tc.join(' ')
}
}
}
class NotIterable {
has #.items
}
my #objs =
Word-Char.new(words => <the sky is blue>),
Names.new(names => ['Jose arat', 'elva delorean', 'alphonse romer']),
NotIterable.new(items => [5, 'five', 'cinco', 'cinq'])
;
for #objs -> $obj {
if $obj.can('LOOP-OVER') {
put "» From {$obj.^name}: ";
for $obj.LOOP-OVER {
.say
}
}
else {
put "» From {$obj.^name}: Cannot iterate over it";
}
}
=begin comment
» From Word-Char:
the => 3
sky => 3
is => 2
blue => 4
» From Names:
Jose Arat
Elva Delorean
Alphonse Romer
» From NotIterable: Cannot iterate over it
=end comment
As stated by jnthn, what approach to use (from the working ones at least) will hardly depend on the problem at hand.
Another (somewhat messy) solution is:
class Word-Char does Iterator {
has #.words;
has Int $.index is rw = 0;
method pull-one() {
LEAVE { $!index++ }
return $!index < #!words.elems
?? (#!words[$!index] => #!words[$!index].chars)
!! IterationEnd;
}
}
my $w = Word-Char.new: words => <the sky is blue>;
my $seq = Seq.new($w).cache;
sub f( $w ) {
.say for $w[]
}
f($seq);
$w.index = 0;
f($seq);
Related
I have an application where I wish to create a new operator to work on one of my custom classes. The definition (sub infix:<α>) compiles without complaint, but when I attempt to use the operator, Raku reports two terms in a row.
I guess there is something weird I'm doing in my module file. Sometimes problems are cured by exiting and re-initializing interactive Raku - but it is perhaps so obvious I can't see it.
**UPDATE
**
Embedding these statements within the module works as expected - this leads me to suspect that I'm doing something wrong in the module, possibly related to exports (the "two terms in a row" message implies the compiler doesn't recognize the customer operators outside the module file).
There's nothing secret or magical about my module:
########################################################################
### Convert goofy old Perl5 module to Raku
########################################################################
unit module Format;
use List::Util;
=begin comment
Class supporting a bidrectional map lookup. Given a hash containing
keys and aliases (e.g., key=>alias) two lookaside hashes are created
to convert between the two values.
=end comment
class Bidirectional is export {
has Any %!aliasMap = ();
has Any %!keyMap = ();
has Any %.map = ();
has Bool $!initialized = False;
=begin comment
Do nothing if this object has already been initialized, otherwise loop
through the supplied map and populate the alias and key maps.
=end comment
method !initialize () {
if !($!initialized) {
for self.map.kv -> $key, $alias {
%!aliasMap{$alias} = $key;
%!keyMap{$key} = $alias;
}
$!initialized = True;
say %!aliasMap;
say %!keyMap;
}
return self;
}
=begin comment
Convert from an alias to a key value
=end comment
method aliasToKey (Str $alias) {
self!initialize();
return %!aliasMap{$alias} || $alias;
}
=begin comment
Convert from a key to an alias value.
=end comment
method keyToAlias (Str $key) {
self!initialize();
return %!keyMap{$key} || $key;
}
=begin comment
Return either a key from an alias (first priority) or an alias from a key
(second priority).
=end comment
method either (Str $candidate) {
return self.aliasToKey($candidate) || self.keyToAlias($candidate);
}
}
sub infix:<α> (Format::Bidirectional $name, Str $value) is export {
return $name.keyToAlias($value);
}
sub infix:<κ> (Format::Bidirectional $name, Str $value) is export {
return $name.aliasToKey($value);
}
sub infix:<∨> (Format::Bidirectional $name, Str $value) is export {
return $name.either($value);
}
class Formatter {
has Str $.span is rw;
has Str $.border is rw;
has Str $.top is rw;
has Str $.reading_order is rw;
has Bool $.locked is rw;
has Str $.left is rw;
has Str $.right_color is rw;
has Str $.font_outline is rw;
has Str $.rotation is rw;
has Str $.shrink is rw;
has Str $.text_wrap is rw;
has Str $.right is rw;
has Str $.bottom is rw;
has Str $.indent is rw;
has Str $.left_color is rw;
has Str $.font_encoding is rw;
has Str $.color is rw;
has Str $.font_script is rw;
has Str $.fg_color is rw;
has Str $.font_strikeout is rw;
has Str $.bg_color is rw;
has Bool $.hidden is rw;
has Str $.align is rw;
has Str $.valign is rw;
has Str $.font_charset is rw;
has Str $.num_format is rw;
has Str $.top_color is rw;
has Bool $.italic is rw;
has Str $.font_family is rw;
has Bool $.bold is rw;
has Str $.bottom_color is rw;
has Str $.size is rw;
has Str $.pattern is rw;
has Str $.font is rw;
has Str $.font_shadow is rw;
has Str $.text_justlast is rw;
has Str $.underline is rw;
=begin comment
Map any properties with aliases. The property name and the alis name
will return the same results with Bidirectional objects.
=end comment
has Bidirectional $!map = Bidirectional.new(:map(
font_encoding => <_font_encoding>;
));
=begin comment
Convert an attribute name such as is used by get_value and set_value
to a property name which keys the .object() hash.
=end comment
method !a2p (Str $candidate) {
return $candidate.substr(2) if $candidate.match(/^\$\W/);
}
=begin comment
Convert the property name to an attribute name that can be used with
get_value and set_value.
=end comment
method !p2a (Str $candidate, Str $prefix='$!') {
return $prefix ~ $candidate if !$candidate.match(/^\$\W/);
}
=begin comment
Given an attribute key, return its attribute alias.
=end comment
method alias (Str $candidate) {
return $!map.keyToAlias($candidate);
}
=begin comment
Given an attribute alias, return its attribute key.
=end comment
method key (Str $candidate) {
return $!map.aliasToKey($candidate);
}
=begin comment
Set the attribute values of the object. Any name supplied in the argument
must be a valid attribute and it must not be a private attribute.
=end comment
method set (%object) {
my #attributes = self.^attributes(:local);
for %object.kv -> $key, $value {
my $attribute = first { $_ ~~ /$key/ }, #attributes;
if $attribute && ($attribute.has_accessor) {
$attribute.set_value(self, $value);
}
}
}
=begin comment
Coverts a Format::Formatter object to a Hash containing only the public
attributes and values.
=end comment
method object (Format::Formatter:D $override?) {
my %answer = ();
my $victim = $override ?? $override !! self;
for $victim.^attributes(:local) -> $attribute {
next if !$attribute.has_accessor;
my $candidate = $attribute.get_value($victim);
my $property = self!a2p($attribute.name);
if $candidate {
%answer{$property} = $candidate;
}
}
return %answer;
}
=begin comment
Determine whether two Format::Formatter objects are the same. This comparison
is carried out against the .object() form of both objects, which excludes any
private attributes. If the two .object() values have the same keys and values,
True is returned; else False
=end comment
method same (Any $other) {
if $other.WHAT ∉ (Hash, Format::Formatter) {
X::TypeCheck.new(operation=>'Format::Formatter comparison',
got=>$other.WHAT, expected=>Format::Formatter).throw;
}
my $left = self.object;
my $right = $other ~~ Format::Formatter ?? $other.object !! $other;
my $answer = $left eqv $right;
return $answer;
}
}
=begin comment
Override === operator for Format::Formatter equivalence - this may have
unforseen results, and the .same() method should probably be used instead
The first form covers the case of $format1===$format2 -- both arguments
are converted to hashes using .object()
The second form covers the case of $format1==$hash2 -- the first argument
is converted to a hash using .object()
=end comment
multi sub infix:<===> (Format::Formatter:D $left, Format::Formatter:D $right) is export {
return $left.object eqv $right.object;
}
multi sub infix:<===> (Format::Formatter:D $left, Hash:D $right) is export {
return $left.object eqv $right;
}
I tried the following. I expect the custom operators to perform the operations I specify.
[2] > my $m = Format::Bidirectional.new(map=>{a => <_a>})
Format::Bidirectional.new(map => (my Any % = :a("_a")))
[3] > say $m.WHAT
(Bidirectional)
[3] > $m ~~ Format::Bidirectional
True
[4] > my $t = 'test'
test
[5] > $t ~~ Str
True
[6] > sub infix:<α> (Format::Bidirectional $name, Str $value) {
return $name.keyToAlias($value);
}
&infix:<α>
[7] > $m α $t
===SORRY!=== Error while compiling:
Two terms in a row
------> $m⏏ α $t
expecting any of:
infix
infix stopper
statement end
statement modifier
statement modifier loop
But using an example from the Raku documentation works:
sub infix:<:=:>( $a is rw, $b is rw ) {
($a, $b) = ($b, $a)
}
my ($num, $letter) = ('A', 3);
say $num; # OUTPUT: «A»
say $letter; # OUTPUT: «3»
# Swap two variables' values
$num :=: $letter;
say $num; # OUTPUT: «3»
say $letter; # OUTPUT: «A»
A
3
3
A
Trying to use the example substituting only my operations (though 'is rw' is spurious here), I get "cannot make assignment" (please note I exited the interaction shell and re-entered before attempting this):
[3] > sub infix:<:==:>($a is rw, $b is rw) { return $a.keyToAlias($b) }
&infix:<:==:>
[4] > $m :==: 'a'
===SORRY!=== Error while compiling:
Cannot make assignment out of := because list assignment operators are too fiddly
------> $m :==⏏: 'a'
I thought perhaps it was the unicode character I used (small alpha) or the class, so:
[0] > use lib "."
Nil
[1] > use Format
Nil
[2] > my $aa = {a=>'_a', b=>'_b'}
{a => _a, b => _b}
[3] > sub infix:<alias>($a, $b) { return $a=>$b }
&infix:<alias>
[4] > $aa alias 'a'
===SORRY!=== Error while compiling:
Two terms in a row
------> $aa⏏ alias 'a'
expecting any of:
infix
infix stopper
statement end
statement modifier
statement modifier loop
And to add insult to injury, thinking a literal term like "alias" might not work:
[4] > sub infix:<swap>( $a is rw, $b is rw ) {
($a, $b) = ($b, $a)
}
my ($num, $letter) = ('A', 3);
say $num; # OUTPUT: «A»
say $letter; # OUTPUT: «3»
# Swap two variables' values
$num swap $letter;
say $num; # OUTPUT: «3»
A
3
3
[4] >
Maybe it was the specific word I was using?
[4] > sub infix:<alias>( $a is rw, $b is rw ) {
($a, $b) = ($b, $a)
}
my ($num, $letter) = ('A', 3);
say $num; # OUTPUT: «A»
say $letter; # OUTPUT: «3»
# Swap two variables' values
$num alias $letter;
say $num; # OUTPUT: «3»
===SORRY!=== Error while compiling:
Two terms in a row
at line 10
------> $num⏏ swap $letter;
expecting any of:
infix
infix stopper
statement end
statement modifier
statement modifier loop
But then I exit and reinitialize:
[3] > sub infix:<alias>( $a is rw, $b is rw ) {
($a, $b) = ($b, $a)
}
my ($num, $letter) = ('A', 3);
say $num; # OUTPUT: «A»
say $letter; # OUTPUT: «3»
# Swap two variables' values
$num alias $letter;
say $num; # OUTPUT: «3»
A
3
3
And for good measure:
my $a = {a=>"_a", b=>"_b"}
{a => _a, b => _b}
[3] > sub infix:<α>( $a is rw, $b is rw ) {
($a, $b) = ($b, $a)
}
my ($num, $letter) = ('A', 3);
say $num; # OUTPUT: «A»
say $letter; # OUTPUT: «3»
# Swap two variables' values
$num α $letter;
say $num;
A
3
3
In the module file I define these custom operators. The subs do not work, the multi subs DO work:
sub infix:<α> (Format::Bidirectional $name, Str $value) is export {
return $name.keyToAlias($value);
}
sub infix:<κ> (Format::Bidirectional $name, Str $value) is export {
return $name.aliasToKey($value);
}
sub infix:<∨> (Format::Bidirectional $name, Str $value) is export {
return $name.either($value);
}
multi sub infix:<===> (Format::Formatter:D $left, Format::Formatter:D $right) is export {
return $left.object eqv $right.object;
}
multi sub infix:<===> (Format::Formatter:D $left, Hash:D $right) is export {
return $left.object eqv $right;
}
I have this code:
class kg is Dimension {
method new() {
return self.bless(
:type('mass'),
:abbr('kg'),
:multiplier(Multiplier.new(
numerator => 1.0,
denominator => Quantity.new(1000.0, 'g')))),
}
}
class mg is Dimension {
method new() {
return self.bless(
:type('mass'),
:abbr('mg'),
:multiplier(Multiplier.new(
numerator => 1000.0,
denominator => Quantity.new(1.0, 'g')))),
}
}
I'll be adding many more similar classes. Rather than spell out all these classes separately, I'd like to learn how to create a factory that can create these classes from simple data structures.
How do I do this? I read the Metaobject Protocol doc but I couldn't figure out how to give my classes different names on the fly based on the examples at the top and middle of the doc page.
I tried:
constant A := Metamodel::ClassHOW.new_type( name => 'A' );
A.^add_method('x', my method x(A:) { say 42 });
A.^add_method('set', my method set(A: Mu \a) { A.^set_name(a) });
A.^compose;
my $bar = A;
$bar.set('Foo');
say $bar.^name; #
A.x; # works
Foo.x; # error
But the last line just throws an error:
Undeclared name:
Foo used at line 13
The first thing you should realize, that any kind of meta-programmming usually will need to be done at compile time, aka in a BEGIN block.
Secondly: at the moment, Raku has some meta-programming features for creating code, but not all features needed to make this as painless as possible. The work on RakuAST will change that, as it basically makes Raku itself being built from a public meta-programming API (rather than you could argue, the current bootstrap version using a lot of NQP).
I've rewritten your code to the following:
sub frobnicate(Str:D $name) {
my \A := Metamodel::ClassHOW.new_type(:$name);
A.^add_method('x', my method x() { say 42 });
A.^compose;
OUR::{$name} := A;
}
BEGIN frobnicate("Foo");
say Foo.^name; # Foo
Foo.x; # 42
So, this introduces a sub called frobnicate that creates a new type with the given name. Adds a method x to it, and composes the new type. And then makes sure it is known as an our in the current compilation unit.
Then the frobnicate sub is called at compile time by prefixing a BEGIN. This is important, because otherwise Foo won't be known when the next line is compiled, so you'd get errors.
There is currently a small catch:
dd Foo.^find_method("x").signature; # :(Mu: *%_)
The invocant constraint is not set. I haven't found a way (before RakuAST) to set that using an meta-programming interface. But I don't think that's going to be an issue for the example you've given. If it does become an issue, then let's cross that bridge when we get there.
Here is the entire code that I came up with for a solution:
#!/usr/bin/env raku
use v6.d;
class Dimension { }
sub dimension-attr-factory($name, Mu $type, Mu $package) {
return Attribute.new(
:name('$.' ~ $name),
:type($type),
:has_accessor(1),
#:is_required(1),
:package($package)
);
}
sub dimension-factory(Str:D $name, #attributes) {
my \A := Metamodel::ClassHOW.new_type(:$name);
A.^add_parent(Dimension);
for #attributes {
my $attr = dimension-attr-factory($_[0], $_[1], A);
A.^add_attribute($attr);
}
A.^compose;
OUR::{$name} := A;
}
class Multiplier {
has Rat $.numerator;
has Quantity $.denominator;
method factor() {
return $.numerator / $.denominator.value;
}
}
class Quantity {
has Rat() $.value is required;
has Dimension:D $.dimension is required;
multi submethod BUILD(Rat:D() :$!value, Dimension:D :$!dimension) {
}
multi submethod BUILD(Rat:D() :$value, Str:D :$dimension) {
$!dimension = ::($dimension).new;
}
multi method new(Rat:D() $value, Dimension:D $dimension) {
return self.bless(
:$value,
:$dimension,
)
}
multi method new(Rat:D() $value, Str:D $dimension) {
return self.bless(
:$value,
:$dimension,
)
}
method to(Str:D $dimension = '') {
my $from_value = $.value;
my $to = $dimension ?? ::($dimension).new !! ::(self.dimension.abbr).new;
# do types match?
if $to.type ne self.dimension.type {
die "Cannot convert a " ~ self.dimension.type ~ " to a " ~ $to.type;
};
my $divisor = $.dimension.multiplier ?? $.dimension.multiplier.factor !! 1.0;
my $dividend = $to.multiplier ?? $to.multiplier.factor !! 1;
my $result = $dividend / $divisor * $from_value;
return Quantity.new($result, $to);
}
method gist() {
$.value ~ ' ' ~ $.dimension.abbr;
}
}
BEGIN {
my %dimensions = 'mass' => {
base => {
abbr => 'g',
},
derived => {
kg => { num => 1000.0, den => 1.0, },
mg => { num => 1.0, den => 1000.0, },
ug => { num => 1.0, den => 1000000.0, },
}
};
for %dimensions.kv -> $key, $value {
# set up base class for dimension type
my $base = %dimensions{$key}<base><abbr>;
my #attributes = ['abbr', $base], ['type', $key];
dimension-factory( $base, #attributes);
my %derived = %dimensions{$key}<derived>;
for %derived.kv -> $abbr, $values {
my $numerator = %dimensions{$key}<derived>{$abbr}<num>;
my $denominator = %dimensions{$key}<derived>{$abbr}<den>;
my $multiplier = Multiplier.new(
numerator => 1.0,
denominator => Quantity.new(1000.0, 'g'),
);
#attributes = ['abbr', $abbr], ['type', $key], ['multiplier', $multiplier];
my $dim = dimension-factory( $abbr, #attributes );
#$dim.new(:$abbr, type => $key, :$multiplier );
}
}
}
my $kg = kg.new();
my $quant = Quantity.new(value => 5.0, dimension => $kg);
dd $quant;
I would probably create a dimension keyword with a custom metamodel, would probably also override * and / operators using undefined dimensions and then would create kg with something like:
dimension Gram {
has Dimension::Type $.type = mass;
has Str $.abbr = "g";
}
dimension KiloGram is Gram {
has Str $.abbr = "kg";
has Dimension::Multiplier $.multiplier = 1000 * g;
}
dimension MiliGram is Gram {
has Str $.abbr = "mg";
has Dimension::Multiplier $.multiplier = g / 1000;
}
but maybe that's too much...
I'm attempting to implement a class that 'does' Positional that also allows me to update its values by assigning to the result returned by the AT-POS method. Eventually, I was able to concoct the following class that works as intended:
class Test does Positional
{
has $.slot_1 is rw = 12;
has $.slot_2 is rw = 24;
method AT-POS(\position)
{
my $t = self;
return-rw Proxy.new:
FETCH => method ()
{
position % 2 ?? $t.slot_1 !! $t.slot_2
},
STORE => method ($v)
{
if position % 2
{
$t.slot_1 = $v
}
else
{
$t.slot_2 = $v
}
}
}
}
my $test = Test.new;
die unless $test[2] == 24;
die unless $test[5] == 12;
$test[7] = 120;
die unless $test[2] == 24;
die unless $test[5] == 120;
$test[10] = 240;
die unless $test[2] == 240;
die unless $test[5] == 120;
Would it be possible to somehow (and: simply) return the container bound to $!slot_1 (or $!slot_2) inside the Test class implementation?
Before I discovered the use of Proxy instances I attempted to return (and return-rw) the result of expression position % 2 ?? $!slot_1.VAR !! $!slot_2.VAR, because I'm under the impression that the VAR method gives me access to the underlying container, in the hope that I can simply return it. That didn't really work, and I do not understand why yet: I suspect it somehow gets coerced back to a value somehow?
So in other words: is it possible to simplify my AT-POS implementation in this particular situation?
Thanks,
Regards,
Raymond.
Assuming you do not want accessors for "slot_1" and "slot_2", and if I understand the question correctly, this would be my implementation. I wouldn't call it a Test class, as that would interfere with the Test class that is used for testing.
class Foo {
has #elements = 24, 12;
method AT-POS(Int:D $pos) is raw {
#elements[$pos % 2]
}
}
my $f = Foo.new;
say $f[2]; # 24
say $f[5]; # 12
$f[2] = 666;
say $f[4]; # 666
Note that the defaults in the array have changed order, that's to keep the arithmetic in AT-POS simple.
Also note the is raw in the definition of the AT-POS method: it will ensure that no de-containerization will take place when returning a value. This allows you to just assign to whatever $f[2] returns.
Hope this made sense!
Also: the Array::Agnostic module may be of interest for you, to use directly, or to use as a source of inspiration.
First off if you aren't going to use an attribute outside of the object, there isn't a reason to declare them as public, and especially not rw.
class Foo {
has $!odd = 12;
has $!even = 24;
…
}
You can also directly return a Scalar container from a method. You should declare the method as rw or raw. (raw doesn't guarantee that it is writable.)
class Foo {
has $!odd = 12;
has $!even = 24;
method AT-POS(\position) is rw {
position % 2 ?? $!odd !! $!even
}
}
# we actually get the Scalar container given to us
say Foo.new[10].VAR.name; # $!even
Note that even if you declare the attributes as public they still have a private name. The private attribute is always rw even if it isn't publicly declared as rw.
class Foo {
has $.odd = 12;
has $.even = 24;
method AT-POS(\position) is rw {
position % 2 ?? $!odd !! $!even
}
}
If you are going to use a Proxy, I would consider moving the common code outside of it.
class Foo {
has $.odd = 12;
has $.even = 24;
method AT-POS(\position) is rw {
# no need to write this twice
my $alias := (position % 2 ?? $!odd !! $!even);
Proxy.new:
FETCH => method () { $alias },
STORE => method ($new-value) { $alias = $new-value }
}
}
Of course the ?? !! code is a core feature of this module, so it would make sense to put it into a single method so that you don't end up with duplicate code all over your class. In this case I made it a private method.
class Foo {
has $.odd = 12;
has $.even = 24;
# has to be either `raw` or `rw`
# it is debatable of which is better here
method !attr(\position) is raw {
position % 2 ?? $!odd !! $!even
}
method AT-POS(\position) is rw {
my $alias := self!attr(position);
Proxy.new:
FETCH => -> $ { $alias },
STORE => -> $, $new-value { $alias = $new-value }
}
}
Again, not much reason to use a Proxy.
class Foo {
has $.odd = 12;
has $.even = 24;
method !attr(\position) is raw {
position % 2 ?? $!odd !! $!even
}
method AT-POS(\position) is rw {
self!attr(position);
}
}
Instead of ?? !! you could use an indexing operation.
method !attr(\position) is raw {
($!even,$!odd)[position % 2]
}
Which would allow for a ternary data structure.
method !attr(\position) is raw {
($!mod0,$!mod1,$!mod2)[position % 3]
}
There was no need to write the if statement that you did as Raku usually passes Scalar containers around instead of the value.
(position % 2 ?? $t.slot_1 !! $t.slot_2) = $v;
Is it possible to use multidispatch for the store method when using a Proxy? In the following minimal example, the code is called when storing an Int
my $foo := do {
my $bar = 1;
Proxy.new:
:FETCH( method { return $bar} ),
:STORE( method (Int $i) { $bar = $i } )
}
say $foo; # 1
$foo = 2;
say $foo; # 2
$foo = "3"; # error, need to pass an Int
But I'd like to handle the STORE differently if given, say, a Str. The work around I've found (other than doing a mega method with given/where is to create a multi sub inside of a block, and return the sub (because a multi method can't be referred to with &foo) with an dummy first parameter:
my $foo := do {
my $bar = 1;
Proxy.new:
:FETCH( method { return $bar} ),
:STORE(
do {
multi sub xyzzy ($, Int $i) { $bar = $i }
multi sub xyzzy ($, Str $i) { $bar = +$i + 1}
&xyzzy
}
)
}
say $foo; # 1
$foo = 2;
say $foo; # 2
$foo = "3";
say $foo; # 4
Is there a better way to do this (mainly for code clarity using method because sub feels...misleading)?
With regards to being misleading: the FETCH and STORE values expecte Callables, which could be either a method or a sub.
Getting back to the question, there is no direct way of doing this, but there is a better indirect way that may be clearer. You can do this by setting up the multi sub first, and then passing the proto as the parameter:
proto sub store(|) {*}
multi sub store(\self, Int) { say "Int" }
multi sub store(\self, Str) { say "Str" }
my $a := Proxy.new(
FETCH => -> $ { 42 },
STORE => &store,
);
say $a; # 42
$a = 42; # Int
$a = "foo"; # Str
And if you want to make the code shorter, but possibly less understandable, you can get rid of the proto (because it will be auto-generated for you) and the sub in the multi (because you can):
multi store(\self, Int) { say "Int" }
multi store(\self, Str) { say "Str" }
my $a := Proxy.new(
FETCH => -> $ { 42 },
STORE => &store,
);
say $a; # 42
$a = 42; # Int
$a = "foo"; # Str
Here's exercise 5.F.2 from 'A Book of Abstract Algebra' by Charles C Pinter:
Let G be the group {e, a, b, b^2, b^3, ab, ab^2, ab^3} whose
generators satisfy a^2 = e, b^4 = e, ba = ab^3. Write the table
of G. (G is called the dihedral group D4.)
Here's a little Perl 6 program which presents a solution:
sub generate(%eqs, $s)
{
my #results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
for #results -> $result { take $result; }
my #arrs = #results.map({ gather generate(%eqs, $_) });
my $i = 0;
while (1)
{
for #arrs -> #arr { take #arr[$i]; }
$i++;
}
}
sub table(#G, %eqs)
{
printf " |"; for #G -> $y { printf "%-5s|", $y; }; say '';
printf "-----|"; for #G -> $y { printf "-----|"; }; say '';
for #G -> $x {
printf "%-5s|", $x;
for #G -> $y {
my $result = (gather generate(%eqs, "$x$y")).first(* (elem) #G);
printf "%-5s|", $result;
}
say ''
}
}
# ----------------------------------------------------------------------
# Pinter 5.F.2
my #G = <e a b bb bbb ab abb abbb>;
my %eqs = <aa e bbbb e ba abbb>; %eqs<e> = '';
table #G, %eqs;
Here's what the resulting table looks like:
Let's focus on these particular lines from generate:
my #arrs = #results.map({ gather generate(%eqs, $_) });
my $i = 0;
while (1)
{
for #arrs -> #arr { take #arr[$i]; }
$i++;
}
A recursive call to generate is made for each of the items in #results. Then we're effectively performing a manual 'zip' on the resulting sequences. However, Perl 6 has zip and the Z operator.
Instead of the above lines, I'd like to do something like this:
for ([Z] #results.map({ gather generate(%eqs, $_) })).flat -> $elt { take $elt; }
So here's the full generate using Z:
sub generate(%eqs, $s)
{
my #results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
for #results -> $result { take $result; }
for ([Z] #results.map({ gather generate(%eqs, $_) })).flat -> $elt { take $elt; }
}
The issue with the Z version of generate is that it hangs...
So, my question is, is there a way to write generate in terms of Z?
Besides this core question, feel free to share alternative solutions to the exercise which explore and showcase Perl 6.
As another example, here's exercise 5.F.3 from the same book:
Let G be the group {e, a, b, b^2, b^3, ab, ab^2, ab^3} whose
generators satisfy a^4 = e, a^2 = b^2, ba = ab^3. Write the
table of G. (G is called the quaternion group.)
And the program above displaying the table:
As an aside, this program was converted from a version in C#. Here's how generate looks there using LINQ and a version of ZipMany courtesy of Eric Lippert.
static IEnumerable<string> generate(Dictionary<string,string> eqs, string s)
{
var results = new List<string>();
foreach (var elt in eqs)
{
if (new Regex(elt.Key).IsMatch(s))
results.Add(new Regex(elt.Key).Replace(s, elt.Value, 1));
if (new Regex(elt.Value).IsMatch(s))
results.Add(new Regex(elt.Value).Replace(s, elt.Key, 1));
}
foreach (var result in results) yield return result;
foreach (var elt in ZipMany(results.Select(elt => generate(eqs, elt)), elts => elts).SelectMany(elts => elts))
yield return elt;
}
The entire C# program: link.
[2022 update by #raiph. I just tested the first block of code in a recent Rakudo. The fourth example returned one result, 'abc', rather than none. This may be due to a new Raku design decision / roast improvement / trap introduced since this answer was last edited (in 2017), or a Rakudo bug. I'm not going to investigate; I just wanted to let readers know.]
Why your use of zip doesn't work
Your code assumes that [Z] ("reducing with the zip operator") can be used to get the transpose of a list-of-lists.
Unfortunately, this doesn't work in the general case.
It 'usually' works, but breaks on one edge case: Namely, when the list-of-lists is a list of exactly one list. Observe:
my #a = <a b c>, <1 2 3>, <X Y Z>; put [Z~] #a; # a1X b2Y c3Z
my #a = <a b c>, <1 2 3>; put [Z~] #a; # a1 b2 c3
my #a = <a b c>,; put [Z~] #a; # abc
my #a; put [Z~] #a; # abc <-- 2022 update
In the first two examples (3 and 2 sub-lists), you can see that the transpose of #a was returned just fine. The fourth example (0 sub-lists) does the right thing as well.
But the third example (1 sub-list) didn't print a b c as one would expect, i.e. it didn't return the transpose of #a in that case, but rather (it seems) the transpose of #a[0].
Sadly, this is not a Rakudo bug (in which case it could simply be fixed), but an unforseen interaction of two Perl 6 design decisions, namely:
The reduce meta-operator [ ] handles an input list with a single element by calling the operator it's applied to with one argument (said element).
In case you're wondering, an infix operator can be called with only one argument by invoking its function object: &infix:<Z>( <a b c>, ).
The zip operator Z and function zip (like other built-ins that accept nested lists), follows the so-called "single-argument rule" – i.e. its signature uses a single-argument slurpy parameter. This means that when it is called with a single argument, it will descend into it and consider its elements the actual arguments to use. (See also Slurpy conventions.)
So zip(<a b c>,) is treated as zip("a", "b", "c").
Both features provide some nice convenience in many other cases, but in this case their interaction regrettably poses a trap.
How to make it work with zip
You could check the number of elements of #arrs, and special-case the "exactly 1 sub-list" case:
my #arrs = #results.map({ gather generate(%eqs, $_) });
if #arrs.elems == 1 {
.take for #arrs[0][];
}
else {
.take for flat [Z] #arrs
}
The [] is a "zen slice" - it returns the list unchanged, but without the item container that the parent Array wrapped it in. This is needed because the for loop would consider anything wrapped in an item container as a single item and only do one iteration.
Of course, this if-else solution is not very elegant, which probably negates your reason for trying to use zip in the first place.
How to write the code more elegantly without zip
Refer to Christoph's answer.
It might be possible with a Z, but for my poor little brain, zipping recursively generated lazy lists is too much.
Instead, I did some other simplifications:
sub generate($s, %eqs) {
take $s;
# the given equations normalize the string, ie there's no need to apply
# the inverse relation
for %eqs.kv -> $k, $v {
# make copy of $s so we can use s/// instead of .subst
my $t = $s;
generate $t, %eqs
if $t ~~ s/$k/$v/;
}
}
sub table(#G, %eqs) {
# compute the set only once instead of implicitly on each call to (elem)
my $G = set #G;
# some code golfing
put ['', |#G]>>.fmt('%-5s|').join;
put '-----|' x #G + 1;
for #G -> $x {
printf '%-5s|', $x;
for #G -> $y {
printf '%-5s|', (gather generate("$x$y", %eqs)).first(* (elem) $G);
}
put '';
}
}
my #G = <e a b bb bbb ab abb abbb>;
# use double brackets so we can have empty strings
my %eqs = <<aa e bbbb e ba abbb e ''>>;
table #G, %eqs;
Here is a compact rewrite of generate that does bidirectional substitution, still without an explicit zip:
sub generate($s, %eqs) {
my #results = do for |%eqs.pairs, |%eqs.antipairs -> (:$key, :$value) {
take $s.subst($key, $value) if $s ~~ /$key/;
}
my #seqs = #results.map: { gather generate($_, %eqs) }
for 0..* -> $i { take .[$i] for #seqs }
}
Here's a version of generate that uses the approach demonstrated by smls:
sub generate(%eqs, $s)
{
my #results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
for #results -> $result { take $result; }
my #arrs = #results.map({ gather generate(%eqs, $_) });
if #arrs.elems == 1 { .take for #arrs[0][]; }
else { .take for flat [Z] #arrs; }
}
I've tested it and it works on exercises 2 and 3.
As smls mentions in his answer, zip doesn't do what we were expecting when the given array of arrays only contains a single array. So, let's make a version of zip which does work with one or more arrays:
sub zip-many (#arrs)
{
if #arrs.elems == 1 { .take for #arrs[0][]; }
else { .take for flat [Z] #arrs; }
}
And now, generate in terms of zip-many:
sub generate(%eqs, $s)
{
my #results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
for #results -> $result { take $result; }
zip-many #results.map({ gather generate(%eqs, $_) });
}
That looks pretty good.
Thanks smls!
smls suggests in a comment below that zip-many not invoke take, leaving that to generate. Let's also move flat from zip-many to generate.
The slimmed down zip-many:
sub zip-many (#arrs) { #arrs == 1 ?? #arrs[0][] !! [Z] #arrs }
And the generate to go along with it:
sub generate(%eqs, $s)
{
my #results;
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
.take for #results;
.take for flat zip-many #results.map({ gather generate(%eqs, $_) });
}
Testing the keys and values separately seems a bit silly; your strings aren't really regexes, so there's no need for // anywhere in your code.
sub generate($s, #eqs) {
my #results = do for #eqs.kv -> $i, $equation {
take $s.subst($equation, #eqs[ $i +^ 1 ]) if $s.index: $equation
}
my #seqs = #results.map: { gather generate($_, #eqs) }
for 0..* -> $i { take .[$i] for #seqs }
}
Obviously with this version of generate you'll have to rewrite table to use #eqs instead of %eqs.