Search code examples
ooprakumethod-signaturemultimethod

Perl 6 multi methods never match expected signature


I have a class with two multi methods (multi submit).

I call my multi like this:

$perspective.submit(:message($message.content));

Which gets shipped off to my class:

my $perspective-api = API::Perspective.new(:api-key(%*ENV<PERSPECTIVE_API_KEY>));

proto method submit (|) {*}

multi method submit(Str :$message!, MODEL :@models = TOXICITY) {
    my $score = $perspective-api.analyze(:@models, :comment($message));
    say @models Z=> $score<attributeScores>{@models}.map: *<summaryScore><value>;

multi method submit(Str :$name!, MODEL :@models = TOXICITY) {
    my $score = $perspective-api.analyze(:@models, :comment($name));
    say @models Z=> $score<attributeScores>{@models}.map: *<summaryScore><value>;
}

However I always get the following response:

Died because of the exception:
    Cannot resolve caller AUTOGEN(Rose::ContentAnalysis::Perspective:D: :message(Str)); none of these signatures match:
        (Rose::ContentAnalysis::Perspective: Str :$message!, MODEL :@models = MODEL::TOXICITY, *%_)
        (Rose::ContentAnalysis::Perspective: Str :$name!, MODEL :@models = MODEL::TOXICITY, *%_)

Despite my named argument (:message) being a Str as required and @models having a default declared.


Solution

  • Multiple dispatch works in two phases:

    • Considering the number of positional parameters and their types
    • If there are any where clauses, named parameters, or sub-signatures, doing a test bind of the signature to see if it would match

    The second phase will reject the candidate if it fails to bind for any reason. One such reason, and I believe the cause of the issue here, is that the default value is wrongly typed. For example, in:

    multi m(:@x = "not-an-array") { }
    m()
    

    We get an error:

    Cannot resolve caller m(...); none of these signatures match:
        (:@x = "not-an-array")
      in block <unit> at -e line 1
    

    But changing it to:

    multi m(:@x = ["an-array"]) { }
    m()
    

    Works fine. (Note that while a default value uses =, it's actually a binding, not an assignment.)

    In the case in the question there's this:

    MODEL :@models = TOXICITY
    

    Looking at the module source the code is taken from, I see:

    enum MODEL is export (
            <TOXICITY SEVERE_TOXICITY TOXICITY_FAST IDENTITY_ATTACK
            INSULT PROFANITY SEXUALLY_EXPLICIT THREAT FLIRTATION
            ATTACK_ON_AUTHOR ATTACK_ON_COMMENTER INCOHERENT INFLAMMATORY
            LIKELY_TO_REJECT OBSCENE SPAM UNSUBSTANTIAL>
    );
    

    Thus TOXICITY is just an Int, but what's expected is a typed array of MODEL values.

    Thus, if you do this:

    multi method submit(Str :$message!, MODEL :@models = Array[MODEL](TOXICITY)) {
    

    It should work.