Hello again!
So, Day 2 is not a particularly interesting problem: basically we have a stream of commands, and we need to move a submarine correspondingly, then in the last step we need to compute the product of the depth and horizontal distance
For problem 2, we are going to have “up N”, “down N” and “forward N”, which will respectively affect the aim of the submarine and the position.
This would be pretty trivial to do with a loop and a case/switch statement (which in Raku is called given/when
) but I think it’s a cool chance to try out Raku’s multimethods.
A class definition in Raku looks like this
class Sub {
has $.aim = 0;
has $.depth = 0;
has $.horizontal = 0;
method process($operation, $num) { ... }
}
The $.
is a twigil which says “this is a scalar value and it’s private but has accessor methods“.§
Methods (and Routines in general) support multiple dispatch which can happen through class, traits, or any specific attribute of the parameter (see the Signature class). In our case, we want to dispatch through the operation name, so we can do this§
multi method process("forward", $num) {
...
}
multi method process("up", $num) {
...
}
multi method process("down", $num) {
...
}
The actual implementation would be: when we go “up” or “down, we want to alter the aim
, and when we go “forward” we move by the given amount horizontally, and by aim * num
vertically.
We could alter the values in the given clasinstance, but… it’s uglier! The declared instance variables are immutable, if we want them to be mutable we should have used the rw
trait, like this
class Sub {
has $.aim = 0 is rw;
has $.depth = 0 is rw;
has $.horizontal = 0 is rw;
}
Raku is pushing gently against us so we prefer immutability, and who am I to go against that?
EDIT: as noted by mscha in the comments, this is incorrect. The variables are mutable, we could always assign them from within the instance, referring to them with the $!
twigil, e.g. $!aim += $num
. The is rw
only refers to generating setter methods so they’re accessible from outside. Sorry, I’m still learning.
So, each of our process()
calls will instead produce a new object by adjusting the current one. Luckily, the language has a pretty nice functionality to help with that: the clone()
method accepts an argument to tweak the cloned object, so our code would look like this
class Sub {
has $.aim = 0;
has $.depth = 0;
has $.horizontal = 0;
multi method process("forward", $num) {
self.clone(horizontal => $.horizontal + $num, depth => $.depth + $num * $.aim)
}
multi method process("up", $num) {
self.clone(aim => $.aim - $num)
}
multi method process("down", $num) {
self.clone(aim => $.aim + $num)
}
}
This looks pretty nice to me! Now we only need to plug this into processing the commands. But I thought, well, we decided to be immutable, right? So let’s do this with reduce.
Rather than use the reduce metaoperator like last time, we can use the actual reduce routine. It works similar to other similar function in Python/Ruby/JavaScript/whatever.
my @words = 'day2.txt'.IO.words; # read all words
my @pairs = @words.rotor(2); # pair them up
my $sub = Sub.new # get a sub
<something with $sub and @pairs>.reduce(<block>) # iterate on values
The problem, is that, from what I understand, we cannot pass an initial value (our $sub
) as an argument. The way to handle that is through using a Slip
.
A Slip allows us to treat a list “as if it was typed there”. For example we can use it to prepend items to a list
> my @list = 1,2,3
[1 2 3]
> my @list2 = 3, @list
[3 [1 2 3]]
> my @list2 = 3, |@list
[3 1 2 3]
Or to use a list as an argument to a multi-argument routine
> my @pair = 1,2
[1 2]
> sub f($a, $b) { say "a: $a, b $b" }
&f
> f @pair
Too few positionals passed; expected 2 arguments but got 1
in sub f at <unknown file> line 1
in block <unit> at <unknown file> line 1
> f |@pair
a: 1, b 2
This is akin to the concept of “splat” in ruby, tho it’s somewhat more formalized.
So, we can pass an initial value to reduce
by simply prepending it to the commands array:
($sub, |@pairs).reduce(<something>)
something
should be a routine that takes two arguments (the accumulator and the next element) and returns a new value for the accumulator. This means it will call process()
, and we can once again use a Slip!
($sub, |@pairs).reduce({ $^a.process(|$^b) })
Notice the $^
sourcery: in Raku you can use the ^
twigil to access variables implicitly, they will just be assigned from the arguments in order of definition. This call is effectively equivalent to
($sub, |@pairs).reduce(-> $a, $b { $a.process($b[0], $b[1]) } )
Now we just have to add a method to compute the final value, which is depth * horizontal
, and putting all together we get
class Sub {
has $.aim = 0;
has $.depth = 0;
has $.horizontal = 0;
multi method process("forward", $num) {
self.clone(horizontal => $.horizontal + $num, depth => $.depth + $num * $.aim)
}
multi method process("up", $num) {
self.clone(aim => $.aim - $num)
}
multi method process("down", $num) {
self.clone(aim => $.aim + $num)
}
method value {
$.horizontal * $.depth
}
}
my @words = '2.txt'.IO.words;
say (Sub.new, |@words.rotor(2)).reduce({ $^a.process(|$^b) }).value
which I find somewhat nice. I have to say, I still don’t like twigils, and I think I’m missing some way to write objects more tersely, if you know Raku better than me please let me know in the comments.
See you next time, happy hacking!
“Raku is pushing gently against us so we prefer immutability, and who am I to go against that?”
I think you’re misinterpreting this.
Raku certainly prefers you to not make the accessors read-write, but it’s perfectly fine to change the attribute value within the class (e.g. `$!aim += $num`).
Immutible classes can be useful / sensible, but not in this case. A submarine isn’t immutable: when you move it, you don’t get a new submarine, but the submarine’s position / aim actually changes.
I agree that Sub is naturally mutable, it’s how I actually solved it at the beginning, and this was just a divertissement 🙂
But I didn’t realize I can use
$!
to access attributes declared with$.
, thanks for the clarification!I’ll update the post when I’ve got a minute.
“we don’t really need accessor methods, but I think this reads better than the alternative $!.”
When I don’t want accessors automatically created for me I like to use `has $foo`. I think `$foo` reads better than `$!`. And you can also then manually create accessors, though I just stick with `has $.bar` to do it for me automatically.
That said, almost no Rakoons use this, and there are occasional rumblings over the years about deprecating it.