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.
Top comments (0)