The purpose of this post is to explain and justify the design features of AL and provide resources for extra reading for interested users.
Design Principles
The AL runtime can be modelled as what we call a ‘commitment machine’, that is, a stack of transactional state machines. Each layer of the state machine provides a different function and has different memory characteristics. These layers can roughly be ordered from most ephemeral to most durable, with state transitions on more durable layers automatically becoming visible to upper, more ephemeral layers. The ephemeral layer must be the most extensible, due to the fact that there is no layer more ephemeral. PROLOG/WAM semantics are used to ensure that you get full backtracking capabilities, bidirectional execution, meaning you can perform exhaustive searches over both your database and in-memory constructs. The ephemeral layer also supports the ephemeral construction of objects through overriding allocation. Thus, unlike other object systems such as ObjVLisp (which AL draws from), new does not collapse into the allocate/initialise dual, but rather a construct/allocate/initialise trio, where construct constructs an ephemeral object, allocate possibly allocates it as a durable object, and initialise performs object initialisation logic.
Objects do not ‘exist’ anywhere in themselves, but rather are abstractions built upon relations which exist within live in-memory tables E.G., ‘the class of :a is :b’, ‘the value of slot :x on :a is 3’, which are hydrated from the on-disk command log table on startup. That is, the object tables are a materialised view of the command log. We ought theoretically be able to support, therefore, other views. We plan to make use of this in order to perform efficient bitemporal queries. The object system is therefore a metaobject protocol represented relationally.
The semantics of AL rely on message sends through inheritance chains. Methods describe admissable transformations over relations, and method clauses are equivalent to predicates in PROLOG, meaning that sends can be bidirectionally executed. This means we can send a message to an anonymous object, with the runtime figuring out which object matches the message, and the ability to ‘broadcast’ messages to all objects that accept the shape of the message comes free! The responsibility of determining how ‘complete’ each method should be (I.E., the forms in which it can be executed) is yours. Each AL run returns the Elixir representation of the state of the runtime along with the bindings discovered during the run (if the transaction succeeded). This is the mechanism by which the user can inspect their result, and backtrack if they so desire.
Each method holds multiple clauses, allowing for backtracking. When an object receives a send, it finds an applicable method object on itself, or via inheritance, and then runs the method. Note that the implementation of send does not permit backtracking over inheritance I.E., only one method object is chosen once an object receives a message. Instead, backtracking, like in PROLOG, is constrained to VM ops such as table lookups (intra-method clause selection, class selection, superclass selection, slot selection, etc). This is so that backtracking over sends is easier to reason about for the user. Multiple inheritance is also supported, as inheritance is simply described as a relation between an object and its superclass, and there can be multiple of such relations. Lookup occurs in order of definition.
All AL programs are wrapped in a database transaction. The purpose of this is to ensure that state changes either all succeed or all fail atomically, ensuring that interleaving processes do not cause interleaving state changes, and helping you to avoid stateful failures. This suggests that general best practice is to cut (same concept as PROLOG cut) whenever you perform state mutations I.E., you should perform a choicepoint commit when you do a transactional commit- otherwise, you may get unintended behaviours, such as multiple instantiation. We don’t perform this cut for you, because there are some scenarios where this may be useful to you. This should be intuitively understood as ‘if lower down in the commitment machine stack something is committed, I should make sure the reasoning I do is against that up to date state’.
The most durable layer within AL is the command log, which exists on-disk and is append-only. However, interactions between AL systems would be considered another kind of transactional state machine, even if it exists outside of AL itself. Since AL is a PROLOG, the command log is intentionally local. It represents your personal, authoritative history of commitments for a single runtime. Distributed runtimes are built through interactions with other systems, not by sharing one distributed command log. In the same way, two different observers may observe events occur in different orders, but it does not change the fact of each observer having total epistemological authority over what they observed. However, we aim to provide plenty of support through libraries to ensure working as a part of a distributed system is as natural as possible. Concurrency is supported via asynchronous message sends. Polling, task dependencies, and more advanced features will be supported in the future.
As the user works on their object system, they will develop new protocols on top of the default send, and make great use of it. In fact they probably won’t want to keep typing out my_send every time they want to run. Thus, dispatch will have to become configurable such that users can define their own lexical contexts which override the default dispatch logic. Therefore AL needs a macro system.
Bibliography
A list of resources that can help the user to understand the hermeneutic that led to the design of AL
Major
Warren’s Abstract Machine - A Tutorial Reconstruction
A Simple Reflective Object Kernel
Update Reconsidered (Here’s a summary by the wonderful Jeremy Taylor of XTDB/JUXT, the paper itself is hard to get a hold of!)
Database, Types, and the Relational Model (The Third Manifesto)
Urbit: A Solid-State Interpreter
Agent-Oriented Programming: From PROLOG to Guarded Definite Clauses
The BEAM Book
On Understanding Data Abstraction, Revisited
Art of the Metaobject Protocol
The Common Lisp Condition System
Object Oriented Programming in Concurrent PROLOG
Minor
Semantic Spacetimes
Pluggable Type Systems
https://bracha.org/pluggableTypesPosition.pdf
Out of the Tar Pit
Sea of Nodes