I have tried to write yangyanzhan's solution of the raku-riddle-contest in raku OOP. Raku class system is very intuitive, and everything work as a charm till I came across a recursive function. This is a version of the code for the class and the function:
class Encounters {
has $.tigers;
has @!encounters;
method encounters {
if @!encounters.elems eq $.tigers {
return [@!encounters, ];
}
my @total_encounters = [] ;
for 1..$.tigers -> $tiger {
if ($tiger / 2) eq ($tiger / 2).round {
@!encounters = ( @!encounters, [$tiger]).flat ;
my @encounter_events = Encounters.new( tigers => $.tigers, encounters => @!encounters ).encounters;
@total_encounters.append: @encounter_events;
}
}
return @total_encounters;
}
}
sub encounters($tigers, @encounters) {
if @encounters.elems eq $tigers {
return [@encounters, ];
}
my @total_encounters = [] ;
for 1..$tigers -> $tiger {
if ($tiger / 2) eq ($tiger / 2).round {
my $sig = ( @encounters, [$tiger] ).flat;
my @encounter_events = encounters( $tigers, $sig );
@total_encounters.append: @encounter_events;
}
}
return @total_encounters;
}
sub MAIN( $tigers ) {
(encounters $tigers, [] ).say;
Encounters.new( tigers => $tigers ).encounters.say;
}
For $tigers = 4, the function gives:
[(2 2 2 2) (2 2 2 4) (2 2 4 2) (2 2 4 4) (2 4 2 2) (2 4 2 4) (2 4 4 2) (2 4 4 4) (4 2 2 2) (4 2 2 4) (4 2 4 2) (4 2 4 4) (4 4 2 2) (4 4 2 4) (4 4 4 2) (4 4 4 4)]
The class, on the other hand, always gets trapped in an infinite loop. I believe the difference between the function and the class lies in this line of code:
@!encounters = ( @!encounters, [$tiger]).flat;
my $sig = ( @encounters, [$tiger] ).flat;
It is unclear to me if this is because of a malformed recursion syntax or the difference in type constrains between functions and classes.
If no type is provided by the user Raku assumes the type to be Any. This includes containers, base-classes, parameters and return types. For containers the default type is Any but the default type constraint is Mu. Please note that binding replaces the container, not just the value.
Destructuring can be used to untangle multiple return values. Raku has many ways to specify a function's return type: Attempting to return values of another type will cause a compilation error. returns and of are equivalent, and both take only a Type since they are declaring a trait of the Callable.
Routines are one of the means Raku has to reuse code. They come in several forms, most notably methods, which belong in classes and roles and are associated with an object; and functions (also called subroutines or sub s, for short), which can be called independently of objects.
Recursion are mainly of two types depending on whether a function calls itself from within itself or more than one function call one another mutually. The first one is called direct recursion and another one is called indirect recursion.
Well it doesn't help that they are not the same algorithm.
They are close, but not the same.
In the subroutine one you have:
my $sig = ( @encounters, [$tiger] ).flat;
but the method instead modifies the attribute with:
@!encounters = ( @!encounters, [$tiger]).flat;
I really don't know how you expected it to behave the same way when one doesn't modify its arguments, but the other one does.
Even more than that, the subroutine one uses Seq
values.
my $sig = ( @encounters, [$tiger] ).flat;
note $sig.raku;
# $((2, 4, 2, 2).Seq)
While the method one uses arrays.
I do not see any benefit to the class as you have it.
It is not a good idea to have a method call that is modifying the structure of the class like you have it.
I would have more of the processing happen at object construction time.
I'm not going try to fix it, or rewrite it, as there are some simple decisions that make me question just how long it would take to unravel the design.
From using string comparisons eq
for comparing numbers.
To calculating division by 2 twice when it could have just been $tigers %% 2
When someone do things like that, it really makes wonder if there aren't larger structural issues that just happen to work out.
The fact that @!encounters
is in an inconsistent state doesn't help things.
I do understand how someone might code that way. In fact I'm sure that I have equally terrible code in my early days.
If I were to try, I would delete the object based one and start cleaning up the subroutine one first. The thought is that by working on that I would get a better idea of the problem space.
I would also have to write a lot of test cases to make sure that it works for numbers other than 4
.
Right now, that is the only input that I know it works for.
There is nothing in the implementation that screams: “this is obviously correct”.
(And like I basically said earlier, there are things that hint at it possibly being incorrect.)
Then I could use that knowledge to try to restructure it to something that could be more easily transferred into an object-based design.
Or I might make it so that the inner values of the subroutine are object based, and thus transfer it slowly to an object based design.
Assuming this was just an exercise of trying to write it in another way, I would suggest messing with the subroutine one a lot more before trying to objectify it.
For example you might try making it so that it doesn't use .flat
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With