How does Perl 6's multi dispatch decide which routine to use?
Asked Answered
G

3

4

Consider this program where I construct an Array in the argument list. Although there's a signature that accepts an Array, this calls the one that accepts a List:

foo( [ 1, 2, 3 ] );

multi foo ( Array @array ) { put "Called Array @ version" }
multi foo ( Array $array ) { put "Called Array \$ version" }
multi foo ( List $list )   { put "Called List version" }
multi foo ( Range $range ) { put "Called Range version" }

I get the output from an unexpected routine:

Called Array $ version

If I uncomment that other signature, that one is called:

Called List version

Why doesn't it call the ( Array @array ) version? How is the dispatcher making its decision (and where is it documented)?

Gae answered 19/6, 2017 at 2:24 Comment(1)
cf say (-> Array @ {}).signature.params[0].typeLysin
G
1

I made a really dumb mistake, and that's why I wasn't seeing what I expected. You can't constrain a variable that starts with @. Any constraint applies to its elements. The Array @array denotes that I have a positional sort of thing in which each element is an Array. This is the same thing that raiph said. The odd thing is that the grammar looks the same but it does different things. It's something I've tripped over before.

Since it's doing something different, it's not going to work out even if the data structure matches:

foo( [ [1], [2], [3] ] );
foo( [ 1, 2, 3 ] );

multi foo ( Array @array ) { put "Called Array @ version" }
multi foo ( Array $array ) { put "Called Array \$ version" }
multi foo ( List $list )   { put "Called List version" }
multi foo ( Range $range ) { put "Called Range version" }

I still get the version I wouldn't expect based on the constraint and the data structure:

Called Array $ version
Called Array $ version

I think this is just going to be one of Perl 6's warts that normal users will have to learn.

Gae answered 1/7, 2017 at 18:55 Comment(3)
Nit: "You can't constrain a variable that starts with @". You can, but it has to be a dynamic constraint using a where clause or subset type. For example multi foo ( @ where *[0] > 2 ) {} would match a foo [4,3,2] call but not foo [2,3,4].Milligan
"The odd thing is that the grammar looks the same but it does different things.". Fundamental to Perls is that their grammars take grammatical number into account. A $ indicates a singular thing. The type to the left of a $var container declaration indicates the type of single thing contained in that container. An @ indicates a plural thing. The type to the left of a @var container declaration indicates the type of the things held in that plural container.Milligan
I wrote: "Nit: "You can't constrain a variable that starts with @". You can..." You can also have a variable be bound to a different container than its usual default type. For example, my %foo is Bag binds %foo to a Bag rather than a Hash. This doesn't (yet?) work for routine parameters (eg sub foo ( @foo is List ) {}) but I decided I may as well be thorough with my nit picking. :)Milligan
M
5

Why doesn't it call the ( Array @array ) version?

Your test foo call has just an Array ([1,2,3]) as its argument, not an Array of Arrays (eg [[1,2,3],[4,5,6]]).

(The @ in @array indicates a value that does Positional, eg an array or a list. Array @array indicates the same thing but with the additional constraint that each element of the array, list or whatever is an Array.)

How is the dispatcher making its decision?

Simplifying, it's picking the narrowest matching type:

multi foo ( Array       )              {} # Narrowest
multi foo ( List        )              {} # Broader
multi foo ( Positional  )              {} # Broader still
multi foo ( @array      )              {} # Same as `Positional`

(Diagram of subtype relationships of Array, List and Positional.)

For lots of details see jnthn's authoritative answer to a related SO question.

(and where is it documented)?

I'm not sure about the doc. Multi-dispatch looks pretty minimal.

Milligan answered 19/6, 2017 at 6:14 Comment(0)
G
1

I made a really dumb mistake, and that's why I wasn't seeing what I expected. You can't constrain a variable that starts with @. Any constraint applies to its elements. The Array @array denotes that I have a positional sort of thing in which each element is an Array. This is the same thing that raiph said. The odd thing is that the grammar looks the same but it does different things. It's something I've tripped over before.

Since it's doing something different, it's not going to work out even if the data structure matches:

foo( [ [1], [2], [3] ] );
foo( [ 1, 2, 3 ] );

multi foo ( Array @array ) { put "Called Array @ version" }
multi foo ( Array $array ) { put "Called Array \$ version" }
multi foo ( List $list )   { put "Called List version" }
multi foo ( Range $range ) { put "Called Range version" }

I still get the version I wouldn't expect based on the constraint and the data structure:

Called Array $ version
Called Array $ version

I think this is just going to be one of Perl 6's warts that normal users will have to learn.

Gae answered 1/7, 2017 at 18:55 Comment(3)
Nit: "You can't constrain a variable that starts with @". You can, but it has to be a dynamic constraint using a where clause or subset type. For example multi foo ( @ where *[0] > 2 ) {} would match a foo [4,3,2] call but not foo [2,3,4].Milligan
"The odd thing is that the grammar looks the same but it does different things.". Fundamental to Perls is that their grammars take grammatical number into account. A $ indicates a singular thing. The type to the left of a $var container declaration indicates the type of single thing contained in that container. An @ indicates a plural thing. The type to the left of a @var container declaration indicates the type of the things held in that plural container.Milligan
I wrote: "Nit: "You can't constrain a variable that starts with @". You can..." You can also have a variable be bound to a different container than its usual default type. For example, my %foo is Bag binds %foo to a Bag rather than a Hash. This doesn't (yet?) work for routine parameters (eg sub foo ( @foo is List ) {}) but I decided I may as well be thorough with my nit picking. :)Milligan
U
0

There seems to be a trade off between the design documents (more complete but more outdated) and the documentation (known to be incomplete, as docs.perl6.org acknowledges, but hopefully more up-to-date). The former explains multisub resolution in Synopsis 12. Excerpt:

When you call a routine with a particular short name, if there are multiple visible long names, they are all considered candidates. They are sorted into an order according to how close the run-time types of the arguments match up with the declared types of the parameters of each candidate. The best candidate is called, unless there's a tie, in which case the tied candidates are redispatched using any additional tiebreaker strategies (see below). [...]

There are three tiebreaking modes, in increasing order of desperation:

A) inner or derived scope

B) run-time constraint processing

C) use of a candidate marked with "is default"

Tiebreaker A simply prefers candidates in an inner or more derived scope over candidates in an outer or less derived scope. For candidates in the same scope, we proceed to tiebreaker B.

In the absence of any constraints, ties in tiebreaker A immediately failover to tiebreaker C; if not resolved by C, they warn at compile time about an ambiguous dispatch. [...]

I don’t know enough about Perl 6 to testify as to its accuracy, but it seems to be in agreement with raith’s answer, and covers additional ground as well.

Unpaged answered 19/6, 2017 at 20:7 Comment(1)
@briandfoy Imo the first part does cover the behavior you showed: "When you call a routine with a particular short name, if there are multiple visible long names, they are all considered candidates. They are sorted into an order according to how close the run-time types of the arguments match up with the declared types of the parameters of each candidate. The best candidate is called, unless there's a tie..." There is no tie so the candidate with the closest matching type wins.Milligan

© 2022 - 2024 — McMap. All rights reserved.