The padlock
module provides several primitives that use atoms
to let you implement most interesting forms of encapsulation and data hiding.
To use this module, you use padlock::new
to create a new lock and a
corresponding key:
padlock->new() -> ($0 -> lock, $1 -> key);
Locks can either be locked or unlocked, and start off locked. To unlock
a lock, you invoke its unlock
method. But importantly, you can only call
this method if you pass in the lock’s key! Providing the wrong key causes the
host to halt and catch fire.
lock->unlock(key) -> (key);
There are a couple of interesting points to note. The first is that you get
both the lock and the key back after invoking unlock
. However, unlock
method is only available if the lock is locked — and so the act of unlocking it
means that the method becomes unavailable. Instead, a new lock
method
becomes available, which you can use to relock the lock. Like unlock
, you
can only invoke lock
by passing in the correct key:
lock->lock(key) -> (key);
The lock and key are both cloneable. Cloning a lock gives you a completely separate lock that can be locked and unlocked with the same key. Cloning a key gives you separate copies of the key that can lock and unlock any of the locks that use the original key.
lock->clone() -> ($0 -> lock_copy);
key->clone() -> ($0 -> key_copy);
Eventually, once you are done with the lock, you should drop it and its key:
lock~>drop();
key~>drop();
On their own, locks and keys don’t really do anything. However, you can use them to hide data, such as private fields in a class.
You can use a normal closure to hide data such that only methods of that particular instance can see it:
{
...
instance = closure containing (field) -> instance;
...
}
instance:
containing (field)
::drop receiving ($return)
{
field~>drop();
$return();
}
But in most languages, private fields have slightly different semantics — they are accessible from the methods of any instance of the same class. You need that to be able to implement an equality function, for instance:
::"==" receiving (other, $return)
{
# How do we break open `other` to get at the `field` that it closes
# over?
}
You could add a “projection” method, which returns all of those fields so that the caller can use them:
::project receiving ($return)
{
$_ = closure containing () -> projected;
$return($_, field);
}
(The projected
result would have a reassemble
method that takes in the
instance and field
and “puts them back together”.)
This works, but it means that anyone at all can call this project
method.
The field is no longer private!
To get the right semantics, you can use a lock and key. You create a single
lock for the type, and include copies of this lock and its key in each instance
of the type. The project
method now takes in a parameter, typically called
proof
. If the caller cannot provide the right key for the type’s lock, then
they are not able to call the project
method, and cannot access the private
fields. If you’re careful never to give the key to anyone else, only instances
of the type are able to pass in the right key to project out the private fields.
The complete example ends up looking something like the following:
$load:
containing ()
receiving ($loaded, padlock)
{
padlock->new() -> ($0 -> private_lock, $1 -> private_key);
padlock~>drop();
$module = closure containing (private_lock, private_key) -> module;
$loaded($module);
}
module:
containing (private_lock, private_key)
::drop receiving ($return)
{
private_lock~>drop();
private_key~>drop();
$return();
}
::new receiving (field, $return)
{
private_lock->clone() -> ($0 -> $private_lock);
private_key->clone() -> ($0 -> $private_key);
$0 = closure containing (private_lock, private_key, field) -> instance;
private_lock = rename $private_lock;
private_key = rename $private_key;
$_ = closure containing (private_lock, private_key) -> module;
$return($_, $0);
}
instance:
containing (private_lock, private_key, field)
::drop receiving ($return)
{
private_lock~>drop();
private_key~>drop();
field~>drop();
$return();
}
::project receiving (proof, $return)
{
# Unlock the private fields. If the caller cannot provide the right key,
# this line causes the host to halt and catch fire.
private_lock->unlock(key <- proof) -> (key -> proof);
# The lock remains unlocked for as long as the instance has its fields
# projected out.
$_ = closure containing (private_lock, private_key) -> projected;
$return($_, proof, field);
}
projected:
containing (private_lock, private_key)
::reassemble receiving (proof, field, $return)
{
# Lock the private fields. If the caller cannot provide the right key,
# this line causes the host to halt and catch fire.
private_lock->lock(key <- proof) -> (key -> proof);
# Reassemble the field into an instance of the type.
$_ = closure containing (private_lock, private_key, field) -> instance;
$return($_, proof);
}