How do I create a subclass of Str?

Viewed 152

I have this class which is basically a wrapper for a Str attribute:

use Vimwiki::File::TextProcessingClasses;
unit class Vimwiki::File::ContentStr;

has Str $!content;

submethod BUILD( :$content ) {
    $!content = $content;
}

method gist {
    return $!content;
}

method capitalize-headers() {
    $!content = Vimwiki::File::TextProcessingClasses::HeadlineCapitalizer.new.capitalize-headers($!content);
}

Seems like it would be more efficient to subclass the core Str class with something like this, similar to a trick I learned for subclassing IO::Path:

class Blah is Str {
    method !SET-SELF() {
        self;
    }

    method new(Str:D $string) {
        self.Str::new($string)!SET-SELF();
    }    
}

my $blah = Blah.new('hello');
say $blah;

However, this throws an error: Default constructor for 'Blah' only takes named arguments

3 Answers

Since constructors in Raku are ordinary methods then - at least if written correctly - they work virtually, so there's no need to declare your own. Subclassing Str is at a minimum:

class SubStr is Str {
}

We can verify it does the right thing as follows:

given SubStr.new(value => "hi") {
    say .WHAT;  # (SubStr) - correct type
   .say;        # hi - correct value
}

This does not appear to be particularly well documented.

Note also that Str is a box around the "native" str, so if the efficiency concern is about memory consumption, then your own boxing around a str will consume no more than Str itself.

unit class Vimwiki::File::ContentStr;
has str $!content;
...

Holy crap, I took a wild guess and passed the $string in with a value argument:

class Blah is Str {
    method new(Str:D $string) {
        self.Str::new(value => $string);
    }    

    method whoa() {
        say self;
    }
}


my $blah = Blah.new('hello');
say $blah.flip;
$blah.whoa # it works!;

Is this documented anywhere?

See the recently merged PR #4105 at https://github.com/raku/doc. It shows a practical example where we create a new class to extend the core class. I cannot say for certain, but I believe the augmented class does NOT change internal state of the base class, so it should be safe for threaded use.

There is a closed Rakudo core PR #5031 which discusses the addition of similar methods to IO::Path. The following example, with a slightly different name and augmented methods with different names, is in a recently published module called IO::Stem. It was published as a module because we could not get a consensus on what to call all parts of IO::Path.basename.

unit class IO::Barename is IO::Path; # IO::Path is the core class
                                     # which is not easily modified
method new(|c) {
    # In this case we must always return the parent object for the
    # subclass
    return self.IO::Path::new(|c);
}

# The subclass will have the augmented method coded in terms
# of the base class
use MONKEY-TYPING;
augment class IO::Path {
    method barename {
        self.extension("").basename;
    }
}
Related