I'm working on a Perl 6 module, Pod::To::Anything
, in an effort to make an easy interface to create Perl 6 Pod formatters. I'm splitting up rendering into multiple render
methods, each having to deal with a given part of the Pod specification.
To ensure the Pod formatters based on this class are complete, I want to add in abstract methods that cover all the possible Pod objects. However, this requires me to use a where
clause a couple of times:
multi method render (Pod::Block::Named:D $ where *.name eq "NAME" --> Str) { … }
I've tried to implement it as follows:
multi method render (Pod::Block::Named:D $pod where *.name eq "NAME" --> Str) { ".TH {self.pod-contents($pod)}\n" }
However, when I try to run the program, Perl 6 complains about a method not being implemented:
===SORRY!=== Error while compiling /home/tyil/projects/personal/perl6-Pod-To-Man/lib/Pod/To/Man.pm6 (Pod::To::Man)
Multi method 'render' with signature :(Pod::To::Man $: Pod::Block::Named:D $ where { ... }, *%_ --> Str) must be implemented by Pod::To::Man because it is required by a role at /home/tyil/projects/personal/perl6-Pod-To-Man/lib/Pod/To/Man.pm6 (Pod::To::Man):7
It's LTA that Perl 6 hides the actual contents of the where
part here, but that is not my main issue. The issue I have is that it tells me a method I did implement is not implemented.
I've golfed it down a little to ensure this is not a specific problem to my current codebase:
role Foo { multi method test(%foo where *<bar>) { … } }
class Bar does Foo { multi method test(%foo where *<bar>) { "Implementation" } }
This code errors with a similar error:
===SORRY!=== Error while compiling /tmp/tmp.o2aoet3JrE/t.pl6
Multi method 'test' with signature :(Bar $: %foo where { ... }, *%_) must be implemented by Bar because it is required by a role
at /tmp/tmp.o2aoet3JrE/t.pl6:5
My question becomes: How can I use abstract multi methods containing a where
clause in Perl 6?
In ABSTRACT class,we can't extends multiple abstract classes at a time. but In INTERFACE, we can implements multiple interfaces at time. Therefore , interfaces are used to achieve multiple inheritance in java.
An abstract class in Java is one that is declared with the abstract keyword. It may have both abstract and non-abstract methods(methods with bodies).
Yes, we can provide parameters to abstract method but it is must to provide same type of parameters to the implemented methods we wrote in the derived classes.
Top Features Of Abstract Class Abstract class needs to have at least one method that is abstract.
TL;DR The issues here relate to the fundamental nature of where
clauses and P6. Rakudo's error message is LTA. You can use where
clauses but you'll have to change how you're using them.
where
clauses and separate compilation of modulesP6 compiles modules. It does not store source code in the modules, including the source code of the where
clause. So when it goes to compare the where
clause in a user's source file that use
s the module containing the role
, with the where
clause in the role, it can't know they're the same so it conservatively decides it can't accept it.
As explained, the compiler does not store the source code in the compiled module. So that's why it's showing where { ... }
.
What it could do is produce an awesome "you can't do that", perhaps with the actual where
clause source code, when compiling the role rather than waiting to inevitably fail when compiling the class doing the role.
I've searched rt.perl.org and the github rakudo repo issues and have not found a corresponding ticket. So I've opened #2146.
subset
sRoutine dispatch is primarily driven by nominal (named) types.
By declaring a subset
you can give a where
constraint a name which you can then plug into signatures thus enabling routine dispatch to do as you wish:
subset Nominal-Type where *.key eq 'bar';
role Foo { multi method test(Nominal-Type $baz) { … } }
class Bar does Foo { multi method test(Nominal-Type $baz) { "Implementation" } }
Bar.new.test: my Nominal-Type $baz = :bar
It's important to note that there's an aged bug that means subsets do not work with variables declared with the explicitly composite sigils (%
and @
).
So you must either use the scalar sigil $
or slash the sigil.
This requirement applies both to the signatures of the abstract methods you write in your roles and to the signatures of the concrete methods your users write.
You can write composite subsets. The example above is a scalar subset, but you could write, say:
subset Nominal-Type-Hash of Hash where *<bar>;
role Foo { multi method test(Nominal-Type-Hash \qux) { … } }
class Bar does Foo { multi method test(Nominal-Type-Hash \qux) { "Implementation" } }
Bar.new.test: my %baz := my Nominal-Type-Hash \qux = { :bar }
Note that I've switched to slashing the sigil in the declaration with the subset type. This is because those using your subset may wish to bind to a new variable with a sigil, as I did in the last line and they might in the body of their methods.
Slashing the sigil instead of using $
ensures that a sigil'd alias is clearly distinct. Users can't, for example, accidentally write the $
sigil'd version of the variable name when they meant to write the %
sigil'd version. Change the name too for extra safety points:
class Bar does Foo { multi method test(Nominal-Type-Hash \qux) { my %baz := qux; # use %baz from here on... } }
So, you can subset an existing nominal type to create a new nominal type that has a distinct name from the existing type and you can (usually will) add a where
clause to that new type:
subset PTA-BN of Pod::Block::Named where *.name eq "NAME"
Now code can use PTA-BN
(or whatever name you choose) as the parameter type constraint. (I think that'll be simpler and less error prone for your users than copying the where
clause anyway.)
Per our discussion in comments below, you'll need to add an is export
to your subsets:
subset PTA-BN is export of Pod::Block::Named where ...
and also a custom EXPORT routine (sub EXPORT { { PTA-BN => PTA-BN } }
) as explained here.
You can build up subsets of subsets of subsets etc:
subset PTA-BN2 of PTA-BN where some-other-condition;
which would ensure that not only is the underlying type of the value at run-time Pod::Block::Named
and its name "NAME" but also that some-other-condition
is true too.
I mention this mainly as a nice prelude to ...
where
clausesWhile routine dispatch is primarily driven by nominal (named) types, necessitating the rest of this answer, there is an exception and it actually involves where
clauses!
Routine dispatch pays attention to a where
clause on a parameter inasmuch as any such clause is considered epsilon (a tiny bit) narrower than not having a clause.
In your original code the corresponding parameter of the role and class methods both had a where
clause so that doesn't apply. cf Signature Introspection.
But this feature enables a nice little twist in your use case. Implementing methods can combine the subset that your role provides written on the left of a parameter with a where
clause the user writes on the right and they will get dibs on matching:
use Your::Module;
class User's-Class does your-role;
multi method render (PTA-BN $pod where foo --> Str) { ... } # first dibs
multi method render (PTA-BN $pod where bar --> Str) { ... } # second dibs
multi method render (PTA-BN $pod --> Str) { ... } # default
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With