Using a Supply to Track the Number of Items in a Box
Tags: rakulang, supply
Published: 2023-04-23 16:06:46

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
        )
    );
  }
}
```

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;
```

Yields the following:

```
Value: 30 / Count: 1
Value: 30 / Count: 2
Value: 30 / Count: 3
Value: 30 / Count: 4
Value: 30 / Count: 5
```

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 }";
});
```

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
```

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 }";
```

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
```

Which is...

```
raku -e 'say 88 + 24 + 29 + 32 + 30'
203
```

...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.