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

3

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

Grison answered 25/7, 2017 at 1:30 Comment(3)
But, the same thing doesn't happen with and Int, for instance. It doesn't do Positional.Grison
you're misusing .does - it expects a type object (or, looking at the implementation, 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 )Tupiguarani
Then .does should complain about that.Grison
E
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

Everson answered 25/7, 2017 at 7:57 Comment(0)
L
5

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.
Loup answered 7/8, 2017 at 4:44 Comment(0)
H
4

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.`

Hefty answered 31/7, 2017 at 21:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.