Padlocks

The padlock module provides several primitives that use atoms to let you implement most interesting forms of encapsulation and data hiding.

Locks and keys

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();

Using locks to hide data

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