Modules · Language · 0.09

Legba

Named slots every package shares, with watchers, locks and compile-time ops.

Importing a slot name gives a package an accessor, and every package that imports the same name reads and writes the same value. A call with a literal name, for a slot that exists at compile time, is compiled to a custom op, and watchers cost nothing until one is added.

package Config;
use Legba qw(app_name debug);

app_name('shop');
debug(0);

package Checkout;
use Legba qw(app_name debug);   # the same two slots, by name

sub banner { app_name() . (debug() ? ' [debug]' : '') }

package main;

Legba::watch('debug', sub { my ($slot, $value) = @_; print "$slot is now $value\n" });

print Checkout::banner(), "\n";   # shop
Config::debug(1);                 # debug is now 1
print Checkout::banner(), "\n";   # shop [debug]

What you get

  • Shared by name

    use Legba qw(app_name) in two packages gives both an accessor to one value. Call it with an argument to set, without to read.

  • Compiled to custom ops

    With a literal name, for a slot made by use Legba, Legba::get and set become custom ops and Legba::index folds to a constant. A name in a variable goes through XS.

  • Watchers, when you want them

    Legba::watch calls back on every set, even to the same value. A slot nobody watches pays nothing for the feature.

  • Lock and freeze

    _lock refuses sets until _unlock, and _freeze refuses them for good. Reads always work.

  • Threads and forks

    After a fork the child has its own copy. Across threads, store a threads::shared value in the slot: Legba is the name, the shared value the storage.

  • For other XS

    _slot_ptr hands over the slot's stable SV*, and _make_get_op and _make_set_op build ops another module puts in its own optree.

In use

use Legba qw(api_base retries);

api_base('https://api.example.com');
retries(3);

Legba::_freeze('api_base');   # for good
Legba::_lock('retries');      # until unlocked

eval { api_base('http://elsewhere') };
print "frozen: ", ($@ =~ /^(.*?) at /)[0], "\n";
eval { retries(10) };
print "locked: ", ($@ =~ /^(.*?) at /)[0], "\n";

Legba::_unlock('retries');
retries(5);
print api_base(), ' tries ', retries(), " times\n";

print join(', ', sort grep { /^(?:api_base|retries)$/ } Legba::slots()), "\n";

Measured in its POD

As the POD records it, on a threaded perl on Apple Silicon: about 55 million reads a second and 142 million writes, through the accessor or a Legba::get or set with a literal name, which compile to the same op.

Get it

cpanm Legba