An illustration of how Raku's junctions are greedy by design, and a proposal.
Raku has a neat feature called Junctions. In this short article I want to highlight a peculiar consequence of the interaction of junctions with functions: they are greedy, by which I mean that they inadvertently turn other arguments of functions into junctions. To illustrate this behaviour, I will create a pair data structure that can take two values of different types, using a closure.
enum RGB <R G B>;
# Pair Constructor: the arguments of pair() are captured
# in a closure that is returned
sub pair(\x, \y) {
sub (&p){ p(x, y) }
}
So pair
takes two arguments of arbitrary type and returns a closure which takes a function as argument. We will use this function to access the values stored in the pair. I will call these accessor functions fst
and snd
:
# Accessors to get the values from the closure
my sub fst (&p) {p( sub (\x,\y){x})}
my sub snd (&p) {p( sub (\x,\y){y})}
The function that does the actual selection is the anonymous subroutine returned by fst
and snd
, this is purely so that I can apply them to the pair rather than have to pass them as an argument. Let's look at an example, a pair of an Int
and an RGB
:
my \p1 = pair 42, R;
if ( 42 == fst p1) {
say snd p1; #=> says "R"
}
So we create a pair by calling pair
with two values, and use fst
and snd
to access the values in the pair. This is an immutable data structure so updates are not possible.
Now let's use a junction for one of the arguments.
# Example instance with a 'one'-type junction
my Junction \p1j = pair (42^43),R;
if ( 42 == fst p1j) {
say snd p1j; #=> one(R, R)
}
What has happened here is that the original argument R
has been irrevocably turned into a junction with itself. This happens despite the fact that we never explicitly created a junction on R
. This is a consequence of the application of a junction type to a function, and it is not a bug, simply an effect of junction behaviour. For a more detailed explanation, see my article "Reconstructing Raku's Junctions".
The Raku documentation of junctions says that you should not really try to get values out of a junction:
"Junctions are meant to be used as matchers in Boolean context; introspection of junctions is not supported. If you feel the urge to introspect a junction, use a Set or a related type instead."
However, there is a FAQ that grudgingly shows you how to do it. The FAQ once again warns against doing this:
"If you want to extract the values (eigenstates) from a Junction, you are probably doing something wrong and should be using a Set instead."
However, as demonstrated by the example I have given, there is a clear use case for recovering values from junctions. It is of course not the intention that one of the values stored in the pair becomes inaccessible simply because the other value happens to be a junction.
I therefore propose the addition of a collapse
function which will allow to collapse these inadvertent junction values into their original values.
if ( 42 == fst p1j) {
say collapse(snd p1j); #=> says 'R'
}
The implementation of this function is taken from the above-mentioned FAQ, with the addition of a check to ensure that all values on the junction are identical.
sub collapse(Junction \j) {
my @vvs;
-> Any \s { push @vvs, s }.(j);
my $v = shift @vvs;
my @ts = grep {!($_ ~~ $v)}, @vvs;
if (@ts.elems==0) {
$v
} else {
die "Can't collapse this Junction: elements are not identical: {$v,@vvs}";
}
}
It would be nice if this functionality could be added as a collapse
method to the Junction
class.
Added a PR: rakudo/rakudo#3944