Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a Perl 6 Str do the Positional role, and how can I change []?

I'm playing around with a positional interface for strings. I'm aware of How can I slice a string like Python does in Perl 6?, but I was curious if I could make this thing work just for giggles.

I came up with this example. Reading positions is fine, but I don't know how to set up the multi to handle an assignment:

multi postcircumfix:<[ ]> ( Str:D $s, Int:D $n --> Str ) {
    $s.substr: $n, 1
    }
multi postcircumfix:<[ ]> ( Str:D $s, Range:D $r --> Str ) {
    $s.substr: $r.min, $r.max - $r.min + 1
    }
multi postcircumfix:<[ ]> ( Str:D $s, List:D $i --> List ) {
    map( { $s.substr: $_, 1 }, @$i ).list
    }

multi postcircumfix:<[ ]> ( Str:D $s, Int:D $n, *@a --> Str ) is rw {
    put "Calling rw version";
    }


my $string = 'The quick, purple butterfly';

{ # Works
my $single = $string[0];
say $single;
}

{ # Works
my $substring = $string[5..9];
say $substring;
}

{ # Works
my $substring = $string[1,3,5,7];
say $substring;
}

{ # NOPE!
$string[2] = 'Perl';
say $string;
}

The last one doesn't work:

T
uick,
(h   u c)
Index out of range. Is: 2, should be in 0..0
  in block <unit> at substring.p6 line 36

Actually thrown at:
  in block <unit> at substring.p6 line 36

I didn't think it would work, though. I don't know what signature or traits it should have to do what I want to do.

Why does the [] operator work on a Str?

$ perl6
> "some string"[0]
some string

The docs mostly imply that the [] works on things that do the Positional roles and that those things are in list like things. From the [] docs in operators:

Universal interface for positional access to zero or more elements of a @container, a.k.a. "array indexing operator".

But a Str surprisingly does the necessary role even though it's not an @container (as far as I know):

> "some string".does( 'Positional' )
True

Is there a way to test that something is an @container?

Is there a way to get something to list all of its roles?

Now, knowing that a string can respond to the [], how can I figure out what signature will match that? I want to know the right signature to use to define my own version to write to this string through [].

like image 938
brian d foy Avatar asked Jul 25 '17 01:07

brian d foy


2 Answers

One way to achieve this, is by augmenting the Str class, since you really only need to override the AT-POS method (which Str normally inherits from Any):

use MONKEY;
augment class Str {
    method AT-POS($a) {
        self.substr($a,1);
    }
}
say "abcde"[3];     # d
say "abcde"[^3];    # (a b c)

More information can be found here: https://docs.raku.org/language/subscripts#Methods_to_implement_for_positional_subscripting

like image 109
Elizabeth Mattijsen Avatar answered Oct 16 '22 22:10

Elizabeth Mattijsen


To make your rw version work correctly, you first need to make the Str which might get mutated also rw, and it needs to return something which in turn is also rw. For the specific case of strings, you could simply do:

multi postcircumfix:<[ ]> ( Str:D $s is rw, Int:D $i --> Str ) is rw {
   return $s.substr-rw: $i, 1;
}

Quite often, you'll want an rw subroutine to return an instance of Proxy:

multi postcircumfix:<[ ]> ( Str:D $s is rw, Int:D $i --> Str ) is rw {
   Proxy.new: FETCH => sub { $s.substr: $i },
       STORE => sub -> $newval { $s.substr-rw( $i, 1 ) = $newval }
}

Although I haven't yet seen production code which uses it, there is also a return-rw operator, which you'll occasionally need instead of return.

sub identity( $x is rw ) is rw { return-rw $x }
identity( my $y ) = 42; # Works, $y is 42.

sub identity-fail( $x is rw ) is rw { return $x }
identity-fail( my $z ) = 42; # Fails: "Cannot assign to a readonly variable or a value"

If a function reaches the end without executing a return, return-rw or throwing an exception, the value of the last statement is returned, and (at present), this behaves as if it were preceded return-rw.

sub identity2( $x is rw ) is rw { $x }
identity2( my $w ) = 42; # Works, $w is 42.
like image 4
BenGoldberg Avatar answered Oct 16 '22 21:10

BenGoldberg