Consider a junction of digits:
say any 0..9; # OUTPUT: any(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
This carries 'any'
as a type of operation and 0..9
as a list of
eigenstates internally. In a smartmatch context, this can match against any
object that can smartmatch against any digit. The list of eigenstates is not
exposed, but there is a means of processing them individually.
The ~~
smartmatch operator delegates to the ACCEPTS
method on the RHS prior
to a Bool
coercion on its result. For example, we'll depend on Code.ACCEPTS
directly as a means of introducing side effects into the smartmatch operation:
sub sum(Mu $topic is raw) {
my $sum is default(0);
sub sum($topic) { $sum += $topic }
&sum.ACCEPTS: $topic;
$sum
}
say sum 2; # OUTPUT: 2
say sum 2 | 2; # OUTPUT: 4
say sum 0 & 4 & 0 & 4 & 0; # OUTPUT: 8
In this case, it forwards a single argument (its "topic") to an invokation of
itself, allowing us to take a sum of eigenstates. We give &sum
a closure to
give us fresh $sum
for each call. Because the $topic
of the inner &sum
has no type, it carries a Mu
type like the outer $topic
, but will
autothread a Junction:D
argument over Any
eigenstates. Because the outer
$topic
is explicitly typed Mu
and is raw
, it will leave both junctions
and containers on input alone.
If we're going to just accept one argument given a junction, it could just as
well be a Mu
eigenstate instead of the junction itself. Like Mu.ACCEPTS
can
thread its topic over Mu
given a type object invocant (instances are NYI;
they default to &[===]
on Any
), Junction.CALL-ME
threads its invocant
over Mu
. Because this will not shortcircuit, these can be chained to
traverse each of the eigenstates of a junction in sequence:
class JMap does Callable {
has &!transform is built(:bind);
multi method ACCEPTS(::?CLASS:U: Mu $topic is raw) {
self.bless: transform => { &^function($topic) }
}
multi method ACCEPTS(::?CLASS:D: Mu $topic is raw) {
&!transform.ACCEPTS: -> Mu $thread is raw { $thread.ACCEPTS: $topic }
}
proto method CALL-ME(Mu) {*}
multi method CALL-ME(::?CLASS:U: Mu $topic is raw) {
self.ACCEPTS: $topic
}
multi method CALL-ME(::?CLASS:D: &function) is raw {
&!transform(&function)
}
}
given JMap(any 0..9) -> &digits {
say 0 ~~ &digits; # OUTPUT: True
say 10 ~~ &digits; # OUTPUT: False
say digits 2 ** *; # OUTPUT: any(1, 2, 4, 8, 16, 32, 64, 128, 256, 512)
}
JMap
gets its one chance to thread a Junction:D
as a type object, so we
forward the junction to map before its Callable
. Should more junctions be
considered, more type objects or a means of typing them for ACCEPTS
would be
necessary. After a CALL-ME
, we wind up with either a callable or a new
junction of callables that is invokable, but does not qualify as Callable
itself. Though ACCEPTS
makes matters complicated, we can drop the Any
bound
autothreading enforces this way.
JMap
can process the eigenstates of a junction generically, but carries
overhead in its threading as a result. If you have a particular Callable
in
mind with which to map, this JTransformer
template can be followed:
class JTransformer does Callable is repr<Uninstantiable> {
multi method ACCEPTS(Mu --> Code:D) { ... }
method CALL-ME(Mu $topic is raw) is raw { self.ACCEPTS: $topic }
}
In general, a CALL-ME
would be used to thread its Mu $topic is raw
through
ACCEPTS
. Instead of instantiating the invocant, this would return a bare
block or anon sub
, which would perform a tailored smartmatch operation given
the threaded Mu
in its context and a topic from any later smartmatch on said
code object.
If a junction produced by CALL-ME
is cached, the ACCEPTS
candidate written
can shoulder part of its resultant thunk's work to make that cheaper to
smartmatch, e.g. by preprocessing the path to a particular block. It can be
cheapened further in this sense by subtyping Mu
directly in lieu of the
default Any
for a simpler dispatch, though it becomes more difficult to work
with in doing so:
class JTransformer is Mu does Callable is repr<Uninstantiable> { ... }
A practical example of a JTransformer
-like class is the internal class Refine
that backs my Kind
subsets'
refinements (where
clauses) as of
v1.0.2.
Because it's dabbling in metaobjects, Any
cannot be assumed (e.g. Mu
,
Junction
), but junctions can allow for complex checks against multiple
metaroles. If a metaobject threaded by its ACCEPTS
call cannot typecheck as
Mu
for any reason, it will substitute a block wrapping a low-level typecheck
against it, otherwise thunking a boolification of an ACCEPTS
call.