!Warning! This is one of my most abstract and part-formed blog posts, a rough sketch of some ideas that are still forming in my mind. You might want to skip onto to something more tangible.
I beleive the ideas expressed in Rich Hickey’s monograph “On State and Identity” (and my own independent grasping toward these ideas) are true.
The past few days I have been contemplating what large scale software look like in this model. The answer seems quite exciting. Traditional immutable versus mutable state conflicts are finally cleared away – instead, each has its defined place.
Values
A value is something time-invariant. All observations of a value should be equivalent. Computationally, a value may be passed around in unresolved or part-resolved form, and further computation is required when parts of the value are accessed.
Entities
An entity is a time-varying stream of values associated with a particular identity. Note that the history of an entity’s value, as well its its current value, is available, which is why a Stream is returned (the history may need to be truncated to fit within real-world computational and storage limit).
def entity[T](id: ID): Stream[T, Timestamp]
Entities change their value transactionally. All changes to the value are recorded permanently. Changes are timestamped in system-time.
def update[T](id: ID, T newValue) //in transaction
Logically, the number of entities is infinite and they are never created or destroyed. If one asks for an entity which has not previously been set to a value, then it returns an empty stream.
Note: There is no way to ask what entities exist. One must find entities via references from other already known entities, and not via a system-level “browse-style” method.
IDs
An opaque immutable value representing an identity. IDs can be used to obtain the values of the entity they reference. IDs are values like any other, can be stored as/in entities, passed to functions, etc.
There is one special identity, system-time. This refers to an entity that holds the current system time, that updates in some discretized approximation of a smoothly changing variable.
Programs
Programs are the things that happen, the things that update entity values and respond to updates in entity state. Most operations are functional, but imperative operations may be required when communicating with external systems, ie for IO.
Functions with Bindings
Functional Programs consist of two things:
- a (purely functional) Function from N inputs to M outputs. It can and should contain other functions nested to any depth.
- a Binding of the function’s inputs and outputs to either
- the value of an entity. In the case of inputs, this means the function is evaluated passing the entity’s value as a parameter. For outputs, this means the entity’s value is assigned that value returned by the function.
- the event of an entity changing value, for inputs only. This means that a function fires when a entity has changed value. (cf FRP)
The ability to launch programs when entities change state is a key to the operation of the paradigm.
Imperative Operations
Programs can also run imperatively, in order to sequence IO operations for communicating without outside systems. They can bind inputs to entity values or entity change events, exactly as functional programs do, but do not return any outputs that are assigned back onto entities.
This explanation is rather terse. I need to give some examples, some diagrams, and some explanations. Perahps some outline of what my goals are. To do anything practical with this paradigm requires alot of infrastructure built on top of this base. For now, Im mainly writing this down as a record of my own thought process, for later re-editing