10

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.

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
kawaii
  • 301
  • 2
  • 8
  • 1
    Could you share the definition of `TOXICITY`? I'm wondering if your default value is impossible to assign. – Jonathan Worthington Jun 25 '19 at 15:49
  • 1
    @JonathanWorthington you can find the MODEL enum within my `API::Perspective` module here, if that is what you are looking for. https://github.com/shuppet/p6-api-perspective/blob/master/lib/API/Perspective.pm6#L32-L37 – kawaii Jun 25 '19 at 15:50

2 Answers2

12

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.

Jonathan Worthington
  • 29,104
  • 2
  • 97
  • 136
8

I see two issues.

One is that you have two methods that are identical except for the name of one named parameter.

Named parameters can have aliases:

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

Note that :$message is really short for :message($message)


Now on the problem which actually prevents your code from working.

@models is a Positional, but you are assigning it a singular value in the signature.

Assign it a Positional, and it works:
(In this case it has to be of type Array[MODEL] because of the MODEL type declaration.)

#                                                           V---------------------V
multi method submit(Str :name(:$message)!, MODEL :@models = Array[MODEL](TOXICITY,)) {
    my $score = $perspective-api.analyze(:@models, :comment($message));
    say @models Z=> $score<attributeScores>{@models}.map: *<summaryScore><value>;
}
Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
  • 2
    Thanks for your response Brad, Jonathan's solution actually solved my core issue but I think you did a good job pointing out code that is probably going to bite me further down the line so I'll take these suggestions into consideration too. :) – kawaii Jun 25 '19 at 16:08
  • @raiph I tried a much smaller subset of the problem when I was testing. – Brad Gilbert Jun 25 '19 at 23:19
  • Hi Brad. But... **Named parameter alias** Are you saying your mention of the named parameter alias aspect should now be understood as purely about possibility for a reader of the code being confused? Or did you see an instance in which things went wrong in your tests? Or foresee a problem even though you didn't see one in your tests? **`Array[Model]`** Do you concur that the key issue with the OP is that a `Bar @baz` parameter or similar must be bound to a `Foo[Bar]` argument, where `Foo` does `Positional`? Perhaps the answers to my questions are obvious to some but not to me. TIA for a reply. – raiph Jun 26 '19 at 08:27