This is my response to the Perl Weekly Challenge #034.
An array slice is just a part (consisting of more than 1 element) of an array (which obviously must also have more than 1 element).
$ raku
> my @values = <zero one twice thrice four fifth VI seventh acht nine X>;
[zero one twice thrice four fifth VI seventh acht nine X]
REPL always gives some output. If we don't ask it to print something for us, we'll
get the result of the last expression that it evaluates. So in this case
that is the array, enclosed in brackets [
and ]
.
If we skip the assignment, we get a slightly different result:
> <zero one twice thrice four fifth VI seventh acht nine X>
(zero one twice thrice four fifth VI seventh acht nine X)
The array above has been replaced by a list, enclosed in parens (
and
)
. The difference between the two data types is that an array can be
changed (called mutable in Raku), whereas a list is read only - or really
write once (immutable).
Back to the challenge. Note that I have given the values values corresponding with their position (and thus index) in the array. I have done so to make it easier to see that we get the right result when we fetch values. Which we'll do now.
> say @values[0 .. 10];
(zero one twice thrice four fifth VI seventh acht nine X)
No surprises there, really. We specified all the indices as a range, and got all the values in the same order. I have used «say», which isn't needed here, because the entire REPL interaction will be summarised as a program later on - as the challenge did ask for a program.
What happens if we request non-existing values?
> say @values[0 .. 12];
(zero one twice thrice four fifth VI seventh acht nine X (Any) (Any))
It doesn't crash, but we get the undefined value
Any
when the value is undefined.
An array slice doesn't have to be in sequence, and we don't have to fetch all the values:
> say @values[10 ... 2];
(X nine acht seventh VI fifth four thrice twice)
A Range (..
) is in increasing order, so I had
to use a Sequence (...
) instead this time.
But we can be even more creative, asking for values in a haphazardly manner:
say @values[7, 4, 1];
(seventh four one)
Now you see the value (pun intended) of having the values correspond to the position (index).
We can request a value several times with the List repetition operator
xx
:
> @values[1, 7 xx 4, 2]
(one (seventh seventh seventh seventh) twice)
That gives us a list inside the main list, making it hard to
iterate over the values. We can slap on a «.flat
» to flatten
everything to a one dimentional list:
> @values[1, 7 xx 4, 2].flat
(one seventh seventh seventh seventh twice)
See
docs.raku.org/routine/xx
for more information about the List repetition operator xx
operator.
See
docs.raku.org/routine/flat
for more information about the flat
method.
We do have the values already, the indices and the
actual values in the array. So let's us it. The hash
method
sounds promising:
> @values.hash
Odd number of elements found where hash initializer expected:
Found 11 (implicit) elements:
...
The problem is that it takes two and two elements, turning them into keys and values, and 11 elements will not work. We can try with 10 elements:
> @values[0..9].hash
{VI => seventh, acht => nine, four => fifth, twice => thrice, zero => one}
That is kind of logical, but not what we were looking for (no indices).
We can use the pair
method to get the
Pairs:
> @values.pairs;
(0 => zero 1 => one 2 => twice 3 => thrice 4 => four 5 => fifth 6 => VI
7 => seventh 8 => acht 9 => nine 10 => X)
Turning an array into Pairs uses the index as the key, and the value as the value. So now we'll have to flip the Pairs to get what we want, as numbers as hash keys doesn' look right (when we are meant to show off a hash slice, as opposed to an array slice).
The invert
method does just that, on all
of them:
> @values.pairs.invert
(zero => 0 one => 1 twice => 2 thrice => 3 four => 4 fifth => 5 VI => 6
seventh => 7 acht => 8 nine => 9 X => 10)
But it is easier (or at least shorter) to use the
antipairs
method:
> @values.antipairs
(zero => 0 one => 1 twice => 2 thrice => 3 four => 4 fifth => 5 VI => 6
seventh => 7 acht => 8 nine => 9 X => 10)
The data type is a Sequence (of Pair objects), as Raku will tell you if you ask:
> @values.antipairs.WHAT
(Seq)
We want a hash, and an assignment gives us that:
my %values = @values.antipairs;
{VI => 6, X => 10, acht => 8, fifth => 5, four => 4, nine => 9,
one => 1, seventh => 7, thrice => 3, twice => 2, zero => 0}
The curlies ({
and }
) shows that we have a hash. Note that
the order is random, as in not the same as the original list (or Sequence).
A hash is an unordered data structure, but it has to return the values in some order
if we ask for them. But do not rely on a specific order. (If you want order, use
«sort».)
Let us get som values:
> say %values<zero>;
0
> say %values<zero VI nine>;
(0 6 9)
Do look up the Pair
type
(at docs.raku.org/type/Pair.
The «pairs», «invert» and «antipairs» methods are also explained there.
The program, as promised:
File: slice-n-dice
my @values = <zero one twice thrice four fifth VI seventh acht nine X>;
say @values[0 .. 10];
say @values[0 .. 12];
say @values[10 ... 0];
say @values[7, 4, 1];
my %values = @values.antipairs;
say %values<zero>;
say %values<zero VI nine>;
This obviously depends on a definition of a dispatch table. But before going down that theoretical road, let us have a look at some alternatives (with progressively better options as we go along).
I'll use a simple (or stupid) calculator as example. It starts with zero as the value, and we can change the value with several commands. The current value is printed before the prompt:
File: pre-dispatch
my $value = 0;
loop
{
my $command = prompt "[$value]: "; # [1]
last if $command eq "q" | "quit"; # [2]
if $command eq "d" | "double"
{
$value *= 2; # [3]
}
elsif $command eq "h" | "half"
{
$value /= 2;
}
elsif $command eq "c" | "clear"
{
$value = 0;
}
elsif $command ~~ /^(\d)$/ # [4]
{
$value = $0.Int; # [5]
}
elsif $command eq "help"
{
say "Please consult a doctor.";
}
}
[1] The handy prompt
command prints the specified
string, waits for user input (terminated by pressing Return) and returns that input.
[2] Note the handy way of listing alternatives with a
«|
». This is actually a Junction, a built in data type. We use
last
to break out of the eternal loop
.
[3] «$value *= 2
» is short for «$value = $value * 2
».
[4] We can set the value to a single digit (0 to 9) by specifying a single digit.
[5] The result of the regex (in [4]) is a Match object, and we coerce it to an integer with the «Int» method. (Note that the assignment would have taken care of it for us, here. But it is generally not a good idea to throw Match objects around.)
That program doesn't look so bad. But wait until someone adds some more commands, and now commands that require several code lines each. Imagine printing the program, finding the opening «{» up front, but the closing «}» several pages later. Or presenting it at a conference...
Let us move the code doing things into procedures, leaving a compact loop (that would fit on a single Powerpoint page):
File: pre-dispatch-sub
my $value = 0;
loop
{
my $command = prompt "[$value]: ";
if $command eq "q" | "quit" { last; }
elsif $command eq "d" | "double" { double; }
elsif $command eq "h" | "half" { half; }
elsif $command eq "c" | "clear" { clear; }
elsif $command ~~ /^(\d)$/ { set($0.Int); }
elsif $command eq "help" { help; }
}
sub double
{
$value *= 2;
}
sub half
{
$value /= 2;
}
sub clear
{
$value = 0;
}
sub set ($new)
{
$value = $new;
}
sub help
{
say "Please consult a doctor.";
}
It is longer, but easier to read. Note that last
is (still) a keyword
(that terminates the loop), but «double» and the rest are procedure calls.
If you don't like the excessive typing of the variable name in the «if»-block, use «given«/«when» instead:
File: pre-dispatch-sub-given (changes only)
loop
{
my $command = prompt "[$value]: ";
given $command
{
when "q" | "quit" { last; }
when "d" | "double" { double; }
when "h" | "half" { half; }
when "c" | "clear" { clear; }
when /^(\d)$/ { set($0.Int); }
when "help" { help; }
}
}
We don't really need the variable $command, so can shorten it:
loop
{
given prompt "[$value]: "
{
...
}
}
We can use a hash, and do look ups there:
File: dispatch
my $value = 0;
my %dispatch = # [1]
(
q => &last, # [2]
quit => &last,
d => &double,
double => &double,
h => &half,
half => &half,
c => &clear,
clear => &clear,
help => &help,
);
loop
{
my $command = prompt "[$value]: ";
if %dispatch{$command} { %dispatch{$command}(); } # [3]
elsif $command ~~ /^(\d)$/ { set($0.Int); } # [4]
}
sub last # [2]
{
exit;
}
sub double
{
$value *= 2;
}
sub half
{
$value /= 2;
}
sub clear
{
$value = 0;
}
sub set ($new)
{
$value = $new;
}
sub help
{
say "Please consult a doctor.";
}
[1] A real dispatch table, without the code clutter of an «if» or «given»/»when» block. Note the & in front of the procedure names, giving the procedure (as a pointer or reference), instead of executing it (and using the return value).
[2] Note that «last» is now a procedure, as we cannot use the «last» keyword.
[3] The actual dispatch. If we have a valid command (registered in
the hash), call it. Note the trainling «()
» telling
Raku to execute the procedure.
[4] Note that this command (or really set of commands; 0 .. 9) isn't set up in the dispatch table, as we'd need 10 entries. We could have done it here, but what if we allow numbers with several digits?
We can specify the code directly, skipping the procedures altogether:
File: dispatch-anon
my $value = 0;
my %dispatch;
%dispatch<q> = %dispatch<quit> = { exit };
%dispatch<d> = %dispatch<double> = { $value *= 2 };
%dispatch<h> = %dispatch<half> = { $value /= 2 };
%dispatch<c> = %dispatch<clear> = { $value = 0 };
%dispatch<help> = { say "Please consult a doctor." };
loop
{
my $command = prompt "[$value]: ";
if %dispatch{$command} { %dispatch{$command}(); }
elsif $command ~~ /^(\d)$/ { $value = $0.Int; }
}
It is shorter, but that is really the only positive thing we can say about this version. It would look plain ugly if we started adding a lot of code to the commands.
Scroll back to the previous version (the file «dispatch»), and consider what would happen if we ditched the dispatch table declaration, and replaced it with let us say two procedures:
sub add-dipatch($label, $coderef) { ... }
sub del-dispatch($label) { ... }
And possibly a third one doing the actual dispatch:
sub dispatch($label) { ... }
Then imagine thinking hard about it, deciding to write a class instead.
Feel free to actually write that class...
That was a digression. I'll have a look at the set command, that is missing in the dispatch table:
File: dispatch-anon-set
my $value = 0;
my %dispatch;
%dispatch<q> = %dispatch<quit> = { exit };
%dispatch<d> = %dispatch<double> = { $value *= 2 };
%dispatch<h> = %dispatch<half> = { $value /= 2 };
%dispatch<c> = %dispatch<clear> = { $value = 0 };
%dispatch<s> = %dispatch<set> = { $value = $0.Int if @_[0] ~~ /^(\d)$/ };
# [1]
%dispatch<help> = { say "Please consult a doctor." };
loop
{
my @command = (prompt "[$value]: ").words; # [2]
if %dispatch{@command[0]}
{
@command[1]
?? %dispatch{@command[0]}(@command[1])
!! %dispatch{@command[0]}() # [3]
}
}
[1] I had to use a command, and chose «set». Note the use of «@_[0]
» to get the first
procedure argument.
[2] As «set» is followed by a value, the digit.
[3] Only pass along the second value, if given. Note that we probably should add an error message wrapped in an «else» here, complaining about an unknown command.
Wikipedia has an article, at en.wikipedia.org/wiki/Dispatch_table. The very first line says it all: «In computer science, a dispatch table is a table of pointers to functions or methods.»
It is worth noting that the first code sample (of two) in that article is in Perl (as of 12. November 2019).
Then we'll have a look at «table». Wikipedia has an article for that as well (at https://en.wikipedia.org/wiki/Table_(information)). The first sentence: «A table is an arrangement of data in rows and columns, or possibly in a more complex structure». The last part isn't very helpful. Further (selective) reading leads me to believe that a hash satisfies the definition.
If that is the case, a «dispatch table» is a «dispatch table» as long as it uses a hash to look up the code to execute. «dispatch», «dispatch-anon», the hinted at code between them, and «dispatch-anon-set» all satisfies this.
If we instead choose a more visual interpretation, that the table must exist physically (so to speak) in the code, we are left with the «dispatch» program is the only one that really satisfies the definition.
And that's it.