TL;DR Junction autothreading is handled by a single central mechanism. I have a go at explaining it below.
(The body of your question starts with you falling into a trap, one I think you documented a year or two back. It seems pretty irrelevant to what you're really asking but I cover that too.)
How junctions get handled
Where is contains( Junction) defined? ... The problem is I can't find [the Junctional] implementation anywhere. ... Since it's autothreaded, it's probably specially defined somewhere.
Yes. There's a generic mechanism that automatically applies autothreading to all P6 routines (methods, operators etc.) that don't have signatures that explicitly control what happens with Junction
arguments.
Only a tiny handful of built in routines have these explicit Junction
handling signatures -- print
is perhaps the most notable. The same is true of user defined routines.
.contains
does not have any special handling. So it is handled automatically by the generic mechanism.
Perhaps the section The magic of Junctions
of my answer to an earlier SO Filtering elements matching two regexes will be helpful as a high level description of the low level details that follow below. Just substitute your 9|21
for the foo & bar
in that SO, and your .contains
for the grep
, and it hopefully makes sense.
Spelunking the code
I'll focus on methods. Other routines are handled in a similar fashion.
method AUTOTHREAD
does the work for full P6 methods.
This is setup in this code that sets up handling for both nqp and full P6 code.
The above linked P6 setup code in turn calls setup_junction_fallback.
When a method call occurs in a user's program, it involves calling find_method
(modulo cache hits as explained in the comment above that code; note that the use of the word "fallback" in that comment is about a cache miss -- which is technically unrelated to the other fallback mechanisms evident in this code we're spelunking thru).
The bit of code near the end of this find_method
handles (non-cache-miss) fallbacks.
Which arrives at find_method_fallback
which starts off with the actual junction handling stuff.
A trap
This code works:
(3,6...66).contains( 9|21 ).say # OUTPUT: «any(True, True)»
It "works" to the degree this does too:
(3,6...66).contains( 2 | '9 1' ).say # OUTPUT: «any(True, True)»
See Lists become strings, so beware .contains()
and/or discussion of the underlying issues such as pmichaud's comment.
Routines like print
, put
, infix ~
, and .contains
are string routines. That means they coerce their arguments to Str
. By default the .Str
coercion of a listy value is its elements separated by spaces:
put 3,6...18; # 3 6 9 12 15 18
put (3,6...18).contains: '9 1'; # True
It's also tested
Presumably you mean the two tests with a *.contains
argument passed to classify
:
my $m := @l.classify: *.contains: any 'a'..'f';
my $s := classify *.contains( any 'a'..'f'), @l;
Routines like classify
are list routines. While some list routines do a single operation on their list argument/invocant, eg push
, most of them, including classify
, iterate over their list doing something with/to each element within the list.
Given a sequence invocant/argument, classify
will iterate it and pass each element to the test, in this case a *.contains
.
The latter will then coerce individual elements to Str
. This is a fundamental difference compared to your example which coerces a sequence to Str
in one go.