Mimosinnet
Mimosinnet

Reputation: 875

Raku type constraints in recursion functions and classes

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.

Upvotes: 7

Views: 115

Answers (1)

Brad Gilbert
Brad Gilbert

Reputation: 34120

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.

Upvotes: 7

Related Questions