5

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 [].

brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • But, the same thing doesn't happen with and `Int`, for instance. It doesn't do Positional. – brian d foy Jul 25 '17 at 06:01
  • 1
    you're misusing `.does` - it expects a type object (or, looking at the [implementation](https://github.com/rakudo/rakudo/blob/43c1767/src/core/Mu.pm#L703..L705), an arbitrary object whose type it will compare against) and not a name: `"some string".does( 'Positional' )` is equivalent to `"some string".does( Str )`, not `"some string".does( Positional )` – Christoph Jul 25 '17 at 08:23
  • Then `.does` should complain about that. – brian d foy Jul 25 '17 at 14:07

3 Answers3

8

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

Tinmarino
  • 3,693
  • 24
  • 33
Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
4

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.
BenGoldberg
  • 415
  • 3
  • 6
3

There's a module that aims to let you do this:

https://github.com/zoffixznet/perl6-Pythonic-Str

However:

This module does not provide Str.AT-POS or make Str type do Positional or Iterable roles. The latter causes all sorts of fallout with core and non-core code due to inherent assumptions that Str type does not do those roles. What this means in plain English is you can only index your strings with [...] postcircumfix operator and can't willy-nilly treat them as lists of characters—simply call .comb if you need that.`

timotimo
  • 4,299
  • 19
  • 23