Supplies and React
While Promises represent single future values and Channels are message queues, Supplies are Raku's solution for streams of asynchronous events. Combined with thereact/whenever syntax, they provide a reactive programming model built right into the language.
What Is a Supply?
A Supply is an asynchronous stream of values that you can tap into. Think of it like an event emitter or an observable:my $supply = .interval(1); # Emits 0, 1, 2, ... every second my $tap = $supply.tap( -> $v { say "Got: $v" }, done => { say "Done!" }, quit => { say "Error: {.message}" }, ); sleep 5; $tap.close;
Supply.interval(1) creates a live supply that emits an incrementing integer every second.
Live vs On-Demand Supplies
Raku has two kinds of supplies:Live supplies emit values regardless of whether anyone is listening. If you tap in late, you miss earlier values:
On-demand supplies replay their sequence for each new subscriber:my $supplier = Supplier.new; my $supply = $supplier.; # This is a live supply $supplier.emit("first"); # No one listening, lost $supply.tap(-> $v { say "A: $v" }); $supplier.emit("second"); # Tap A sees this $supply.tap(-> $v { say "B: $v" }); $supplier.emit("third"); # Both A and B see this $supplier.done;
my $on-demand = supply { emit 1; emit 2; emit 3; }; $on-demand.tap(-> $v { say "First subscriber: $v" }); $on-demand.tap(-> $v { say "Second subscriber: $v" }); # Both get 1, 2, 3
The Supplier Pattern
Supplier is the write end. Supply is the read end. This separation keeps things clean:
my $supplier = Supplier.new; my $supply = $supplier.; # Consumer side $supply.tap(-> $msg { say "Received: $msg" }); # Producer side $supplier.emit("hello"); $supplier.emit("world"); $supplier.done; # Signal completion
react/whenever
Thereact block combined with whenever gives you structured reactive code:
Themy $supplier = Supplier.new; start { react { whenever $supplier. -> $value { say "Got: $value"; } } say "React block finished"; }; $supplier.emit(1); $supplier.emit(2); $supplier.emit(3); $supplier.done; # This causes the react block to finish sleep 1;
react block stays alive until all its whenever sources are done or the block is explicitly left.
Multiple Whenever Clauses
A singlereact block can listen to multiple supplies:
my $numbers = Supplier.new; my $letters = Supplier.new; start { react { whenever $numbers. -> $n { say "Number: $n"; } whenever $letters. -> $l { say "Letter: $l"; } } }; $numbers.emit(1); $letters.emit('a'); $numbers.emit(2); $letters.emit('b'); $numbers.done; $letters.done; sleep 1;
Supply Combinators
Supplies support powerful transformation operations:my $supplier = Supplier.new; my $supply = $supplier.; # Grep -- filter values my $errors = $supply.grep(*.starts-with('ERROR')); # Map -- transform values my $lengths = $supply.map(*.chars); # Unique -- deduplicate my $unique = $supply.unique; # Batch -- collect values into groups my $batched = $supply.batch(elems => 3); $errors.tap(-> $v { say "Error: $v" }); $lengths.tap(-> $v { say "Length: $v" }); $supplier.emit("INFO: all good"); $supplier.emit("ERROR: disk full"); $supplier.emit("ERROR: network down"); $supplier.done; sleep 1;
Throttling and Timing
my $supplier = Supplier.new; my $supply = $supplier.; # Stable -- wait for a pause in emissions my $debounced = $supply.stable(0.5); # 500ms debounce $debounced.tap(-> $v { say "Stable: $v" }); # Rapid-fire emissions $supplier.emit("a"); $supplier.emit("b"); $supplier.emit("c"); # Only "c" will be emitted after 500ms pause sleep 2; $supplier.done;
Merging Supplies
Combine multiple supplies into one:my $s1 = Supplier.new; my $s2 = Supplier.new; my $merged = .merge($s1., $s2.); $merged.tap(-> $v { say "Merged: $v" }); $s1.emit("from s1"); $s2.emit("from s2"); $s1.emit("also s1"); $s1.done; $s2.done; sleep 1;
Supply.interval for Timers
Create periodic events:react { # Emit every 2 seconds whenever .interval(2) -> $tick { say "Tick $tick at {DateTime.now}"; done if $tick >= 4; # Stop after 5 ticks } } say "Timer done";
Practical Example: File Watcher
# Watch a directory for changes sub watch-dir(::Path $dir) { supply { whenever .interval(1) { state %seen; for $dir.dir -> $file { my $mod = $file.modified; if !%seen{$file} || %seen{$file} != $mod { %seen{$file} = $mod; emit "$file modified at $mod"; } } } } } # Usage: # react { # whenever watch-dir("/tmp".IO) -> $event { # say $event; # } # }
Practical Example: Event Bus
Supplies andclass EventBus { has %!suppliers; method on( $event --> ) { %!suppliers{$event} //= Supplier.new; %!suppliers{$event}. } method emit( $event, $data) { if %!suppliers{$event} { %!suppliers{$event}.emit($data); } } } my $bus = EventBus.new; $bus.on('user:login').tap(-> $user { say "Login: $user" }); $bus.on('user:logout').tap(-> $user { say "Logout: $user" }); $bus.on('user:login').tap(-> $user { say "Audit: $user logged in" }); $bus.emit('user:login', 'Alice'); $bus.emit('user:logout', 'Bob'); $bus.emit('user:login', 'Carol'); sleep 1;
react/whenever give Raku a reactive programming model that feels natural and composable. They are the right choice for event streams, real-time data processing, and any situation where you need to respond to asynchronous events.