DEV Community

Xliff
Xliff

Posted on

Using a Supply to Track the Number of Items in a Box

It all starts with a box. Every so often the box has a certain number of things put into it. What would be the best way to track the number of items in the box with Raku?

The obvious answer would be to use $*SCHEDULER.cue, wouldn't it? But what happens when I have another box with another set of things that are put into it.

The other option is Supply.interval, which would work great if the number of items at each interval is 1... which it isn't.

Introducing Supply.interval-with-value

Which takes the normal .interval, and adds a value parameter to it:

class IntervalValue does Tappable {
    has $!scheduler;
    has $!interval;
    has $!delay;
    has $!value;

    submethod BUILD(
        :$!scheduler, 
        :$!interval, 
        :$!value,
        :$!delay 
        --> Nil
    ) { }

    method tap(&emit, &, &, &tap) {
        my $i = 0;
        my $lock = Lock::Async.new;
        $lock.protect: {
            my $cancellation = $!scheduler.cue(
                {
                    CATCH { 
                        $cancellation.cancel if $cancellation
                    }

                    $lock.protect: { 
                        emit [ $!value, $i++ ] 
                    };
                }, 
                :every($!interval), 
                :in($!delay)
             );
             my $t = Tap.new({ $cancellation.cancel });
             tap($t);
             $t
        }
    }

    method live(--> False) { }
    method sane(--> True) { }
    method serial(--> True) { }
}

use MONKEY-TYPING;

augment class Supply {
  method interval-with-value(
    Supply:U: 
      $interval, 
      $value, 
      $delay = 0, 
      :$scheduler = $*SCHEDULER
  ) {
    Supply.new(
        IntervalValue.new(
            :$interval, 
            :$delay, 
            :$value, 
            :$scheduler
        )
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Not one to throw away data, this supply has two parameters. The first parameter is the value, which is the amount of items going into the box. The last parameter is the count, which is the same as the original Supply.interval.

So doing the following:

Supply.interval-with-value(2, 30).tap( -> *@a {
  say "Value: { @a.head } / Count { @a.tail.succ }";
});
sleep 10;
Enter fullscreen mode Exit fullscreen mode

Yields the following:

Value: 30 / Count: 1
Value: 30 / Count: 2
Value: 30 / Count: 3
Value: 30 / Count: 4
Value: 30 / Count: 5
Enter fullscreen mode Exit fullscreen mode

This now works a treat!

Of course... what happens when one needs value to change? Do we need to refactor?

Well.... not really.

You see...value is untyped.

So it can be anything.

Like a closure!

So something like this would work just fine:

my $scalable = sub { 30 * $bonus };

Promise.in(5).then({ 
  $bonus = 2;
  say "Scalable is now { $scalable() }";
});

Supply.interval-with-value(2, $scalable).tap( -> *@a {
  say "Value: { @a.head.() } / Count { @a.tail.succ }";
});
Enter fullscreen mode Exit fullscreen mode

Running the above code produces:

Value: 30 / Count: 1
Value: 30 / Count: 2
Scalable is now 60
Value: 60 / Count: 3
Value: 60 / Count: 4
Value: 60 / Count: 5
Enter fullscreen mode Exit fullscreen mode

Which works just fine.

Putting it all together...

So I now have everything I need to handle my box counting.

class Box {
  has $.count is rw = 0;
}

my $b = Box.new;

my $items = 30;
my $counter = sub { $items };

Supply.interval-with-value(2, $counter).tap( -> *@a {
  my $delta = @a.head.();
  say "Count += { $delta }";
  $b.count += $delta;
});

# Introduce randomness by changing count 4 times
for ^4 {
  Promise.in($_ * 2).then({
    my $delta = (-5..5).grep( *.so ).pick;
    say "Adjusting item count by $delta";
    $items +=  $delta;
  });
}
sleep 15;

say "Final count: { $b.count }";
Enter fullscreen mode Exit fullscreen mode

Running this new code, I get the following:

Count += 30
Adjusting item count by 2
Count += 32
Adjusting item count by -3
Count += 29
Adjusting item count by -5
Count += 24
Adjusting item count by -2
Count += 22
Count += 22
Count += 22
Count += 22
Final count: 203
Enter fullscreen mode Exit fullscreen mode

Which is...

raku -e 'say 88 + 24 + 29 + 32 + 30'
203
Enter fullscreen mode Exit fullscreen mode

...correct!

So this is one way to increment box by an arbitrary delta at a specific interval. Here's hoping this helps you solve one of your mysterious use cases one day.

Top comments (0)