5

I want to build a class in Raku. Here's what I have so far:

unit class Vimwiki::File;

has Str:D $.path is required where *.IO.e;

method size {
    return $.file.IO.s;
}

I'd like to get rid of the size method by simply making my class inherit the methods from IO::Path but I'm at a bit of a loss for how to accomplish this. Trying is IO::Path throws errors when I try to create a new object:

$vwf = Vimwiki::File.new(path => 't/test_file.md');

Must specify a non-empty string as a path
  in block <unit> at t/01-basic.rakutest line 24
Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
StevieD
  • 6,925
  • 2
  • 25
  • 45

1 Answers1

5

Must specify a non-empty string as a path

I always try a person's code when looking at someone's SO. Yours didn't work. (No declaration of $vwf.) That instantly alerts me that someone hasn't applied Minimal Reproducible Example principles.

So I did and less than 60 seconds later:

IO::Path.new

Yields the same error.

Why?

The doc for IO::Path.new shows its signature:

multi method new(Str:D $path, ...

So, IO::Path's new method expects a positional argument that's a Str. You (and my MRE) haven't passed a positional argument that's a Str. Thus the error message.

Of course, you've declared your own attribute $path, and have passed a named argument to set it, and that's unfortunately confused you because of the coincidence with the name path, but that's the fun of programming.

What next, take #1

Having a path attribute that duplicates IO::Path's strikes me as likely to lead to unnecessary complexity and/or bugs. So I think I'd nix that.

If all you're trying to do is wrap an additional check around the filename, then you could just write:

unit class Vimwiki::File is IO::Path;

method new ($path, |) { $path.IO.e ?? (callsame) !! die 'nope' }

callsame redispatches the ongoing routine call (the new method call), with the exact same arguments, to the next best fitting candidate(s) that would have been chosen if your new one containing the callsame hadn't been called. In this case, the next candidate(s) will be the existing new method(s) of IO::Path.

That seems fine to get started. Then you can add other attributes and methods as you see fit...

What next, take #2

...except for the IO::Path bug you filed, which means you can't initialize attributes in the normal way because IO::Path breaks the standard object construction protocol! :(

Liz shows one way to workaround this bug.

In an earlier version of this answer, I had not only showed but recommended another approach, namely delegation via handles instead of ordinary inheritance. I have since concluded that that was over-complicating things, and so removed it from this answer. And then I read your issue!

So I guess the delegation approach might still be appropriate as a workaround for a bug. So if later readers want to see it in action, follow @sdondley's link to their code. But I'm leaving it out of this (hopefully final! famous last words...) version of this answer in the hope that by the time you (later reader) read this, you just need to do something really simple like take #1.

raiph
  • 31,607
  • 3
  • 62
  • 111
  • 1
    Yes, ran into a bunch of problems trying to inherit from IO::Path. I was just in #raku-beginner discussing this. lizmat thinks IO::Path may not be subclassable and thinks it's something that should be reported. – StevieD Jan 31 '22 at 16:28
  • 1
    I don't currently see a reason the delegating approach would not work. Have you tried it? It achieves "class that inherits the same methods as `IO::Path`" if you interpret "inherit" as including delegation, which some folk do. It's *selective* inheritance of course (per the `handles` argument), and your class won't be a drop-in type-check passing replacement for `IO::Path` as a subclass might, but might work for your use case? .oO ( Maybe *both* subclassing `IO::Path` *and* making the new class's path attribute be an `IO::Path` typed delegate would work? ) – raiph Jan 31 '22 at 18:07
  • 1
    Oh, I'm not saying that won't work. I'm just curious as to how/why doing straight inheritance was a problem even after getting rid of the $.path attribute. Trying my best to learn the ins and outs of this stuff. See https://climatechangechat.com/vimkiki-files.html – StevieD Jan 31 '22 at 18:44
  • And I just tried your suggestion. Seems to work: `unit class Vimwiki::File; has IO::Path $.delegate handles *; has $.path; multi method new($path) { self.bless(:$path, delegate => IO::Path($path)); }` Please let me know if I screwed that up or if there is a better way. – StevieD Jan 31 '22 at 19:07
  • 1
    OK, I more closely followed your suggestion than what was in the docs you linked to. I got it now. See https://climatechangechat.com/vimkiki-files.html#solution-thanks-raiph I *think* I'm good now. Thanks! – StevieD Jan 31 '22 at 19:29
  • Interesting. Keep me posted. I'm intrigued. – StevieD Jan 31 '22 at 22:25
  • OK. I hope to comment on your journey and delegation solution in the next day or two. Now I've read the issue, the penny has dropped about `IO::Path` misbehaving. I feel for you having to face that down. Ouch! Thanks for sticking with it and filing the issue. I've updated my answer accordingly yet again, hopefully for the last time. – raiph Jan 31 '22 at 23:39