Modular Manipulation of the Link-Time Environment

I’m in the process of hammering out a concrete design for Awelon, and my design efforts have taken me to some interesting places. My current application model looks like this:

  • An Awelon application consists of a set of anonymous modules.
  • Each module will directly manipulate the link-time environment.
  • When the link-time environment stabilizes, the runtime behavior can be extracted.

Modules collaboratively specify a runtime behavior. Each module is fully responsible for its own link-time behavior. This contrasts with more traditional approaches to modularity, where modules are passive objects to be integrated by some unspecified third party. The link environment provides the sandbox for the modules to work. It may provide stateful resources, but it’s ultimately confined.

This design would be problematic for a procedural programming model due to non-deterministic ordering issues. But Awelon is a reactive demand programming language, and thus has synchronous concurrency. All the effects are applied at the same logical time.

I imagine that most modules will not directly contribute to runtime behaviors. Instead, many modules will contribute knowledge or components to shared locations in the link environment, e.g. parts of a world map or dialog tree, components of a UI specification, pages for the web service, constraints, defaults, overrides. Developers may then use link-time metaprogramming to construct the runtime behavior.

The link environment is cleanly partitioned from the runtime environment using the type system, and may be discarded after use. Logically, the environment is still present (otherwise the extracted dynamic behavior would expire). But since the environment is constant, and the extracted behavior won’t change (without a corresponding change in code), the environment can be discarded. (Though we might keep it around for live programming.)

This design supports a style I call stone soup programming. The modules are the ingredients. Developers can easily toss modules into a project to extend behavior or contribute flavor. The modules will link themselves under their own power, without any built-in constraints model.

As a disadvantage: it’s easy to ruin a soup.

Direct composition of the ingredients (e.g. clam beef stew chowder) doesn’t always work out. It’s an experiment. To address this, Awelon enables decomposition of an application into multiple component apps, each of which build in a separate link environment then are applied or composed in the parent. Usefully, the integrated app is subject to further optimization.

Given high levels of link-time metaprogramming, isolating a problem can become a hassle. This issue is partially addressed by a good development environment (e.g. ability to browse the link environment) and partially by discipline (one responsibility, clear documentation). Decomposition might help.

Individually, each module goes through a similar process to parse and extract link-time behaviors. (By handling each module independently, we ensure that at least the parse is independent of the application, and we support caching.)

Overall, this system is simpler, more consistent, more transparent, and more flexible than my earlier efforts. Some of my earlier approaches had constraint solvers built into the linkers, effectively forcing developers to learn another programming model. Here, I might provide constraint solvers in the link environment, but their use is voluntary.

The main concern I have is that linking is now a first-class concern, and developers might not be used to that sort of staged reasoning. But I believe this can be addressed via user-defined syntax, e.g. creating languages for cases where linking becomes boiler-plate.

Posted in Language Design, Modularity, Open Systems Programming, Security, State | 3 Comments

Why Not FFI

FFI – foreign function interface – is a common way for new languages to integrate with existing systems. But FFI is problematic in many ways. FFI represents ambient authority (the ability to ‘import’ authority to ad-hoc resources without a specific grant) which makes it difficult to integrate untrusted code. FFI requires more information about the platform (i.e. that it provides the foreign language), which hinders portability and cross-compilation. FFIs integrate awkwardly with GC, runtime plugins, concurrency, debuggers, rich types, etc..

Instead of FFI, we should define programs against well-defined interfaces, which may vary based on the kind of the application.

For example, for end-user apps, we define assume a DOM-like API, and the application would have no authority not granted by that API. This isn’t a new idea. JavaScript, Lua, and other scripting languages have been doing similar for decades. Potentially, we might even have freedom to decide a compilation target, e.g. JavaScript and HTML vs. C++ and Wx.

The integration code is shifted outside the language. The program has well-defined authorities and contextual requirements, making it secure-able and portable.

On a per app-type basis, the integration burden is lower because we’re dealing with a specific API and context rather than a generic one-size-fits-all FFI. We don’t need to deal with generic type adapters, generic GC integration, etc.. There is some opportunity for specialized optimizations. The overall integration cost (across many kinds) will be higher, but not too much: while there are many ‘kinds’ of applications (end-user apps, web servers, pubsub apps (ROS, DDS), console apps, dock apps, etc.), there are a great many apps of each kind, and the overhead of supporting a new kind is readily amortized. And the backend itself might be extensible or pluggable, at least for common capabilities like network and storage.

One reason I’m thinking about this regards use of import expressions – avoidance of them.

Gilad Bracha argues against imports in his article Ban on Imports. His argument is based on abstraction and extension issues: imports tie modules to concrete implementations of potentially abstract dependencies. While those are valid issues, I’m more concerned about entanglement: imports create a web of names, and it is difficult to extract a module for reuse in another project without dragging along enormous sections of that web; and name or version conflicts easily result in DLL hell.

FFI without import seems not very practical. Also, FFI can be understood as a form of import. To be rid of imports, we must be rid of FFI. And that’s just one more motivation to avoid FFI.

Posted in Language Design, Modularity, Open Systems Programming, Security | 3 Comments

Declarative State Machines

Lately, I’ve been wondering what state-machines would look like if specified for declarative systems.

Traditional state machines are specified to receive a stream of events as input. Each event may shift the machine into a new state. The transitions are programmed into the machine, and are commonly static in nature. Outputs from a state machine are typically based on the current state (a Moore machine) or on the transitions (a Mealy machine). These state machines are a good fit for imperative programming models. Serialization into an event stream fits imperative semantics due to the implicit passage of time across imperative actions. Such state models work best with a single writer, no concurrency. If there are multiple writers, it will be difficult to form a deterministic stream.

In 2011, I contemplated state machines for declarative systems and developed reactive state transition (RST). While RST can serve the same role as state machines, it is a very different programming experience, inverting many aspects of the traditional state machine. Instead of providing a stream of events, developers manipulate the RST program by adding and removing edges, and allow the state to flow through the program naturally. It’s essentially live coding applied to state machines.

RST was designed as an external state model, i.e. conceptually separate from the client application. There are many reasons to avoid local state, and it is necessary for use with my reactive demand programming model. In 2011, I was having some difficulty addressing external state models without making them generic. Fortunately, my recent designs towards ad-hoc external state will allow something much closer to a traditional state machine, having a static user-defined program (a fixed set of edges and potential states).

So I’m once again contemplating state machines for declarative systems.

The state machine will be influenced by declarations by agents external to it. Each declaration might apply inputs to the machine. There is no implicit passage of time between declarations, and multiple declarations easily overlap in time and influence concurrently. Thus, we might understand the state machine as being influenced by a sequence of frames where each frame is a set of inputs.

To keep the simplicity of traditional state machines, declared inputs should must from a simple input alphabet. For example, if the input alphabet is `a..f`, then a valid frame would be `acd`. The sequence of observations by the state machine is a series of frames, e.g. `acd, abd, cef, ce, cbd, bd, abd`. The input alphabet may represent observations, propositions, or control signals.

From there, we can almost directly apply the traditional state machine, simply using the powerset of the declarative input alphabet. I.e. we can draw edges and specify transitions such that frame `cef` is treated as a completely distinct event from `ce` or `cdef`.

The exception regards continuity. Frames represent continuous observations. Thus, we must not distinguish `acd, acd, acd, abd, abd, abd, abd, cef, cef` from `acd, abd, cef`. This exception can be addressed easily enough: if a state has an input with a given event, it must not have an output on the same event. Alternatively, we could analyze for loops globally, or settle loops deterministically. If developers need to distinguish a constant frames, they might consider adding a discrete clock signal, e.g. `acd, acdk, acd, acdk`.

That’s it! Nothing more is necessary to achieve declarative state machines. For output, both Mealy and Moore machines are useful in appropriate contexts (e.g. Mealy machines are useful for adapting imperative models, while Moore machines are better for composition).

Of course, a powerset can get impractically large, very quickly. We’ll want a shorthand to describe large sets of possible frames. We could use logical propositions: `a & c & ~b & ~e` includes the set of frames {ac, acd, acf, acdf}, and could easily be shorthanded to `ac-be`.

Interestingly, use of non-deterministic machines (NFAs) may prove more convenient and symmetric. An NFA essentially allows that we have a set of active states (usually understood to be possible states), just like we have a set of active inputs. This makes it easier to address signals individually. Potentially, we could address loops by simply activating every state reachable in the loop. This design also composes well in a declarative system with the Moore machine, since we could have a set of active output signals.

Posted in Concurrency, Open Systems Programming, State | Leave a comment

Ad-Hoc External State Models

Encapsulation, Accessibility, Security

One of the main reasons we encapsulate state is to gain more control over how it updates. For example, take your traditional, imperative, mutable state variables that respond to get and put actions. We might use one or two of these variables to model a queue, or a stack. Queues and stacks have invariants that help developers reason about correctness, performance, fairness, and other properties of the larger program in which they are used. But in an open system, assuming these variables are accessible, someone might develop a new agent, plugin, or process that starts mucking around with the variables and breaking those invariants. By encapsulating state – placing it under control of a uniquely identified subprogram (object or module) – we can easily protect our invariants.

The cost is uniform semantic accessibility. If we extend the program with independent entities they cannot (modulo semantic hacks like pervasive reflection, monkey patching, debug builds) access the state in ways that were not designed. We could depend on programmer discipline and foresight to ensure a good design, but those are precious commodities and shouldn’t be spent frivolously on common problems. Uniform semantic accessibility can help with many common problems: orthogonal persistence, runtime upgrade, live coding, plugins and extensions.

I believe semantic accessibility is too great a price to ask. Consequently, I argue strongly for eschewing local state wherever feasible – including implicit local state such as message queues, synchronization, promises, stateful event loops. Of course there are limits on how far you can take that advice in an architecture not designed for it. What I’m really arguing for is a change in language and architecture designs.

Fortunately, encapsulation is not the only way to protect state invariants.

Consider: we can understand filesystems, databases, HTTP, etc. as providing extremely global, imperative mutable state variables with get and put actions. Such variables are abundant and more than adequate for dynamic systems. For example, you can generate filenames or URLs based on existing client IDs, pairs of IDs, existing state, etc.. And those state variables can be secured, e.g. using cryptographic IDs (based on PKI or HMAC) or sparse capabilities. State can be addressed for any stateful programming task you can imagine. If you want to model a stack or a state machine, designate a few variables for it. If you want to model a queue for every client of a service, designate variables based on the client ID and a server ID. Secure external state effectively solves the same problems as encapsulated state, without hindering extension.

Secure external state seems to have a very different experience and intuition than local state. You lose access to the concepts of creation and destruction, i.e. the ‘new’ operator of OOP. You must always provide a name for a resource. Creation is replaced with naming elements in abundant resource volumes. Destruction is replaced with either reset operations (which clear resources to a state that can be recovered implicitly) or predictable decay behaviors (e.g. exponential decay). I don’t see these experiential issues as better or worse, but they do take a little getting used to.

Abstraction and Behavior Encapsulation

But there remains at least one valid concern: abstraction. When we encapsulate a couple variables into a queue, we now have this entity we can uniquely understand as a queue. We do not need knowledge of its underlying implementation. If we model an external queue using state variables, how might we share and understand our queue as just a queue?

A deeply unsatisfying answer might be: make a queue a primitive. It’s a valid answer. We shouldn’t assume that ‘get or put’ mutable variables are the only primitives available. It’s also an effective, practical answer: need queues or streams? add them. Need relational tables? add them. We can get wherever we need to be, one primitive at a time. The ‘level’ at which we program will rise. It isn’t so uncomfortable to implement a stack or queue above a relational table. But this answer is unsatisfying because it kicks the can, eschews abstraction, and overly empowers the architect.

A better answer is behavior encapsulation, which can be separated from state encapsulation. A stateless program can gather data from external sources, distribute updates, and generally orchestrate ad-hoc dataflows. It is feasible to support distribution and utilization of these programs – e.g. as first-class functions, dynamic behaviors, or stateless objects. We could share a behavior that provides a queue-like interface above a few mutable state variables.

But behavior encapsulation introduces its own challenge. There is overhead to make the behavior available to its users. There are concerns regarding performance, partial failure, and consistency. Extensions might bypass the view. Ability locally reason about correctness is diminished.

Programmable State

It would be most convenient to simply have a queue wherever we need a queue. Similar can be said of stacks, or histories, or various state machines. If we’re to avoid a panoply of primitives, we’ll need to specify ad-hoc behaviors for our state resources. That means programming state.

The mechanics of programmable, external state are no challenge. We need a few programmable primitives to bootstrap the system. We might update state resource Foo with a script that affects the update behavior of state Bar. Or we could have a resource self-modify its behavior from some programmable initial state. If we desire static behavior, we can even specify update behavior directly in a resource identifier. Static behavior might work well enough for simple machines or parametric variations on a theme, and could feasibly allow state transfer as an alternative to behavior upgrade.

Programmable state comes with risks, of course. There is potential for divergence (which severely undermines persistence). Encapsulation still causes problems for extension. It may be difficult to change state update behaviors at runtime without losing work or entering an inconsistent state. The developer will depend on discipline and foresight to address such concerns.

Those risks can be addressed. The issues with local state are most severe when it’s pervasively mixed with the dataflow or orchestration logic. We could easily enforce an isolated update language, i.e. such that future state is a pure function of present state and inputs. We could further constrain the state update language to avoid divergence. If we ensure the resulting models are readily reusable and widely useful – such that developing new ones becomes a rare case – then relying on discipline and foresight is acceptable.

Programmable External State for RDP

Not every programming model is suitable for avoiding local state. For many, local state is pervasive, implicit, or both. Eventful programming styles generally require state for every non-trivial computation, to an extent that state is effectively local and pervasive no matter how you model it. (I advise against events for future programming languages and architectures.) Data streaming (e.g. streaming a large file over time) tends to have similar issues unless the data is naturally time-aligned (like video-frames or sound).

I am developing a paradigm called Reactive Demand Programming (RDP). An RDP program is an internally stateless behavior that orchestrates signals between external resources, which may be stateful. RDP developers are strongly discouraged from use of ‘change’ signals; instead, they should report continuous, time-varying states, queries, or control signals. To model events, I suggest an idiom of modeling a stateful log of events, then using a signal to report the state of that log. RDP is highly suitable for time-aligned data.

RDP’s interaction with stateful resources is quite simple: a signal may contain information about the current state of a resource, and the set of active signals reaching a resource can influence its present and future state. The origin, history, and future of specific signals, and replication of a signal, must not affect observable behavior (a constraint to achieve highly valuable commutativity, idempotence, and merge equivalence properties).

A consequence is that RDP needs new state models. Imperative, mutable state variables with get and put actions are a very poor fit for RDP. Further, all of them need to be external.

Since initially developing RDP, I’ve been contemplating how to resolve the state issue. I’ve sketched various state models such as history with exponential decay, reactive state transition, and animated term rewriting. I plan to implement a fair number of these. This would be a daunting task without generically programmable state.

Fortunately, it isn’t difficult to represent programmable state models. My current implementation of RDP is a Haskell project called Sirea. The user-programmed aspect of the state model can be modeled with typeclasses, while unique type identifiers become part of the resource identity (via Data.Typeable). A lightweight sketch for time-independent discrete declarative state – complete with orthogonal support for persistence and history – looks something like:


-- Every state has a history. State has a unique identity, rsc. State is
-- updated by signals of type i, which is run in a fixpoint loop in any
-- model of continuous time. 
class (Hist rsc s, Eq s, Ord i) => State rsc i s | rsc -> i s where
    update    :: rsc -> Set i -> s -> s

-- Support for both exponential decay of history, and windowed
-- history. A halflife of zero or less will result in no history 
-- being kept past expiration. 
class (Typeable rsc) => Hist rsc s | rsc -> s where
    intial    :: rsc -> s -- initial value (infinite history)
    mergehist :: rsc -> DT -> s -> s -- decay merge
    halflife  :: rsc -> DT -- exponential decay rate after expiration.
    expire    :: rsc -> DT -- windowed history; decay starts after

-- persistent state
class (State rsc i s, Serializable rsc, Serializable s) => PState rsc i s

-- declare state available in a particular partition
class (Ord ty, Partition p) => DeclState ty p

observeState :: (PState ty i s, DeclState ty p) => ty -> B (S p ()) (S p s)
observeHist :: (PState ty i s, DeclState ty p) => ty -> DT -> DT -> B (S p ()) (S p [(T,s)])
influenceState :: (PState ty i s, DeclState ty p) => ty -> B (S p i) S1

I describe the need for fixpoints in declarative state way back with reactive state transition.

Anyhow, something like the above, perhaps with a few variations for different kinds of state models (e.g. discrete animated state models, which may change incrementally despite constant influence), will probably become part of the Sirea state package, enabling developers to easily introduce new, ad-hoc state models while getting persistence, history, and other nice features implicitly. I really like the idea of uniform access to history. It may similarly be worth building in anticipation as a uniform feature, which would barely be any extra effort.

Posted in Concurrency, Language Design, Modularity, Open Systems Programming, Reactive Demand Programming, State, Types | 1 Comment

Progress Report: Loops and Restarts in Sirea

I’ve been working on Sirea quite a bit in the last few months, albeit making less progress than I had hoped. Mostly, I’ve been running in circles.

In Reactive Demand Programming, all loops are indirect, via a shared external resource (e.g. shared state or a demand monitor). A simple behavior that might loop is: monitor >>> bfmap maxPlusOne >>> bdelay 0.1 >>> demand, using a demand-monitor resource. The delay of 0.1 seconds means the loop logically runs at 10Hz. (A loop without delay is potentially divergent and usually a bug.) In Sirea, a loop must be broken across steps in a thread. This means the thread hosting this behavior must physically be running at an average rate of at least 10 steps each second, though the physical frequency may be decoupled from the logical frequency (e.g. running in bursts).

At the time of my progress report, I had loops working robustly, but not efficiently. I achieved this by making a conservative estimate: if a resource could possibly be in a loop, I treated it as though it were in a loop. I.e. `demand` always broke updates across steps, even if there was no local cycle. Breaking updates across steps increases update latency and risk of rework downstream (non-optimal update orderings, more visible intermediate values). Ideally, I’d want to do as much work in each step as possible.

In what may be a fine example of foolishly premature optimization, I’ve over the last few months been modifying Sirea to support thread-local cycle detection and making a few other tweaks. This required a big, painful, sweeping overhaul.

Partition-local cycle detection is now achieved dynamically at each step: each resource that could be part of a cycle sends out a pulse for cycle detection. If it hears its own pulse then it is part of a cycle. Each cycle only needs to be ‘cut’ at one point – the first to detect it – which allows pipelines within a greater cycle. This works alongside the `touch` mechanism, which warns of impending updates to achieve a near optimal update ordering within each partition thread. (Cross-partition cycles are implicitly broken across steps, just like everything else that crosses partitions.)

An issue I found while doing this work regarded what happens when the process is frozen by an external source, then restored. I work with a laptop that suspends automatically when I close the lid, so I experienced a phenomenon where – upon opening the lid – I’d get a sudden burst of CPU for several seconds while the system tries to ‘catch up’ with real-time. I had modeled this by basically sending a signal that says: “oops! retroactively correct the last few hours; we were inactive at the time!”. (It does this if it detects a jump more than a few seconds.) But this correction does not fix stability within the loop. The problem: a loop with delay 100ms can only increase its logical stability 100ms per full cycle. After being suspended for an hour, I’d need to perform 36k full cycles for stability to catch up. That’s a lot of processing to happen all at once, though it wouldn’t have been a problem if it was distributed over the hour.

I’m currently considering ways to either mitigate this issue or fully correct for it. One way to mitigate the problem: increase stability by a small fraction (e.g. 0.25%) of the difference between stability and update, when the difference is greater than a few seconds. This would reduce the number of steps to a logarithmic function of time. A potential way to correct the problem: make the resources a bit smarter for this particular issue, able to see when there is a huge period of inactivity.

Anyhow, that’s what I’ve been working on for Sirea. Hopefully I’ll get to more interesting stuff (e.g. sound, UI) soon.

Posted in Modularity, Reactive Demand Programming, Stability | Leave a comment

Objects as Dependently Typed Functions

One issue I have with objects is that I cannot compose them like functions (i.e. `f . g . h`). The reason for this is the method-set associated with each object – it requires a lot of wiring to compose two objects, and it is awkward to express or apply that wiring in a higher-order manner.

An alternative model for objects is as a dependently-typed function (or a dependently typed procedure). The basic constraint for such an object model is that every method essentially have a distinguishable input type. This isn’t terribly difficult to achieve, since we can use a `newtype` mechanism to wrap equivalent types for different roles, and thus method names would often become newtype identifiers.

Wiring between objects of different types or kinds may still difficult, but now can itself be expressed as a simple object/function that can be composed like every other object/function.

Posted in Language Design | Tagged , | Leave a comment

Anticipation in RDP Reduced

Some time ago, I described anticipation in RDP. In that article, I envisioned a behavior intially called `anticipate`, that I eventually shortened to `bpeek`. For discrete-varying signals, it might be specialized to `bpeekL`.

-- observe a specific distance into a signal's future
-- return Nothing when signal is inactive in said future
bpeek :: DT -> B (S p a) (S p (Maybe a))

-- observe all discrete signal values up to signal's future, with
-- its current value then future updates. 
bpeekL :: (Eq a) => DT -> B (S p a) (S p (a,[(T,Maybe a)]))

Unfortunately, it seems this form of anticipation interferes with an equivalence property, which I’ll call ‘merge equivalence’. Merge equivalence is the dual to spatial idempotence.

  • Merge Equivalence: forall foo . (foo +++ foo) >>> bmerge = bmerge >>> foo
  • Spatial Idempotence: forall foo . bdup >>> (foo *** foo) = foo >>> bdup

Sirea only supports merge equivalence if the input signals are already synchronized. In an ideal RDP language, latencies would be built right into the type system and any desired synchronization would therefore be explicit prior to merge, so this caveat would be unnecessary. Sirea supports spatial idempotence more generally, though developers must be careful when using a few unsafe behaviors (which are documented thus). Unfortunately, spatial idempotence is not leveraged for optimizations in Sirea.

(Aside: I use the phrase spatial idempotence to describe replicated concurrent operations. Temporal idempotence would describe behavior of sequential operations, i.e. values `f` for which (f . f) = f. RDP does not promise temporal idempotence of behaviors, though specific behaviors may have that property, and (under the hood) signal updates are temporally idempotent, which is useful for robust communication.)

Merge equivalence is a very valuable equivalence property. It allows developers to reason about conditional expressions, and case-decomposition of a problem domain. Further, merge equivalence is essential for dynamic behaviors. Whenever an evaluation behavior switches from one dynamic behavior to another, we want the transition to be seamless. If `foo` is equivalent to `bar`, then switching from foo to bar should not even be observable (and we can’t cheat by comparing for equivalence; behaviors are opaque).

The contradiction between bpeek and merge equivalence is obvious when presented as an example:

-- may observe periods of inactivity due to switching
(bpeek 0.1 +++ bpeek 0.1) >>> bmerge 

-- switching is no longer observable after merge
bmerge >>> bpeek 0.1

Consequently, I’ve decided to eliminate anticipation as a plain behavior in RDP. This is a blow to RDP’s expressiveness. But I believe this does not greatly undermine the utility of speculative evaluation and anticipation in RDP. Many use-cases for anticipation are isolated to resources anyway:

  • pre-load files (e.g. textures, configurations) that we’ll need in the near future
  • smooth motion of a robotic arm based on anticipated commands
  • anticipate future constraints to improve stability of planners or constraint systems
  • leverage anticipated updates to improve predictions on a stateful world model

Resources can freely leverage anticipated demand without hurting merge equivalence. And developers can leverage anticipation through resources. Some resources, especially world models, may allow direct queries about anticipated future states.

Posted in Reactive Demand Programming, State | Leave a comment

Exponential Decay of History, Improved

In an earlier article, I described exponential decay of history as an alternative to ring buffers. In this article, I’ll provide a full recap and an improved design.

Exponential decay of history is a pattern that competes with ring-buffers, least-recently-used heuristics, and other techniques that represent historical information in a limited space. Like ring buffers, exponential decay can keep useful historical information in a bounded space. However, ring buffers provide a flat history, e.g. the last ten elements (perhaps corresponding to a few seconds), which means they can quickly lose historical context (such as what was happening minutes ago, hours ago, days ago) that may be very valuable for making useful decisions. Exponential decay provides a deep history – potentially keeping years or centuries of context in a bounded volume. The tradeoff is losing much of the intermediate information.

Exponential decay essentially models information with a half-life, where the half-life can be measured in terms of a frame count (or an event count, though I disfavor events). For example, if the half-life is 50 frames, then a buffer of 1050 frames will keep historical context for twice as long as a buffer of 1000 frames. At 30 Hz, half-life 50, buffer 1000, the system will keep information for roughly 2^(1000/50)/30 seconds = 10 hours. If we bump the buffer to 2000 frames, it could keep one thousand years. (Aside: Users don’t need to cap the buffer size. If they don’t, then they lose bounded-space and real-time guarantees, and performance will predictably decay logarithmically with the system’s lifetime.)

When we model information with a half-life, it is essential to decide which half to keep. Ultimately, we need a ThunderGrid loss function: two frames will enter, one will leave. The loss function can be modeled in many ways:

  1. We can blindly keep one of the two frames, e.g. the second one (`const const`).
  2. We can select one frame by some preference properties, e.g. the longer-lived frame, or the more interesting one.
  3. We can merge two frames. E.g. perhaps keeping a summary of counts, minima, and maxima recorded in frames. It’s important that the size of the merged frame is about the same size of the frames being merged, i.e. bounded space, or an approximation of it. (If we concatenated frames, we would effectively keep a complete history.)
  4. We can perform possibilities 2 or 3 within some context – e.g. a record of younger and older frames, perhaps some state. This context can help improve decisions about which information is redundant and which is interesting.

Which loss function we need will depend on the application. By tuning the loss-function, half-life, and buffer size (and data type, of course), users of exponential decay of history can accommodate the needs of almost any application. Exponential decay of history has an enormous range of potential applications. Consider:

  • system logs – logs (for services, operating systems, etc.) are often modeled in ring-buffers, e.g. a ring of two or three files that each keep up to 10MB of log events. The ring-buffer model is effective for bugs that occur at a higher frequency than the buffer is replaced, but exponential decay could efficiently enable diagnosis of very low-frequency or rare events.
  • video game save files – Today, people keep a few saves and tend to round-robin between them. Unfortunately, this can lead to a frustrating situation where players get ‘stuck’ – i.e. where all their saves are in a bad spot. Exponential decay could easily address this: each save slot is a ‘frame’, and (with a small half-life) the history could easily run the whole length of a game (even hundreds of hours) with just 10-20 saves. Players could then load from earlier points without difficulty. Usefully, this is also compatible with automatic saves.
  • memoization and caching – a simple technique to improve performance of many systems is to remember previous calculations (memoization) or to remember recent communications (caching). But memoization and caching both introduce problems of: how long do we keep the representation in memory? Keeping all representations is generally a waste of resources because there is no guarantee we’ll reuse a value. Common bounded-space techniques, such as round-robin or least-recently-used, enable easy (even accidental) construction of cases where they behave poorly. Exponential decay is a very nice alternative. Each frame represents a lookup * representation pair. We add a new frame only if it does not exist in the history. Frequent frames will naturally have more opportunities, and thus greater probability, of reaching the higher-numbered frames positions where they will be remembered for longer. Lower-numbered frames would generally correspond to the most recent lookup operations. Thus, over time, the cache achieves a valuable balance of the old-but-common tasks and the recent tasks.
  • document history and undo – Documents tend to undergo lots of small edits in short bursts. Providing those minute edits in the short term is useful for robust ‘undo’, whereas a coarse-grained history is valuable for long-term records. This aligns well with exponential decay of history, with each frame recording document state. Every document could be kept with a deep history in a bounded space that’s no more than a constant factor larger than the largest size reached by the document.
  • debugging with history – Debuggers in traditional procedural languages traditionally provide the current stack of operations, which offer some hints about what happened but aren’t so useful for recording callbacks. The stack really is not about history, but about the future – what is the the program going to do next? This becomes obvious when languages are implemented in a continuation passing style (CPS) or with the tail call optimization (TCO). These styles can hide even the small amount of historical context offered by a stack. I have even seen flame wars involving people objecting to CPS and TCO because of how much it hinders debugging. The history is important. One approach to address the issue is to keep a history more explicitly – e.g. recording activations of function-calls with their arguments. But keeping history is expensive, so we limit the space. People solving this in the past have used a ring-buffer. Unfortunately, an inner loop deep in some algorithm can easily overrun the whole ring-buffer, so we still lose valuable history. If we instead used exponential decay to record activations, we’ll potentially achieve much greater access to historical contexts. But we can do better! Instead of function call events each frame could be a snapshot of the program state. This would enable time-traveling debuggers to keep long histories in bounded space, and thus making them usable even in deployed systems.
  • long-running statistics and attributes – There are many reasons we might wish to keep running statistics in a system, whether for performance profiling, world modeling, or diagnosis. Useful stats often include minima, maxima, counts, running totals, sums of squares, and so on. For exponential decay of history model, each frame is a record of statistics, and the loss-function is trivial composition: the min of minima, the max of maxima, the sums of counts or sums. In addition to the normal statistics, each frame can be indexed with the period it covers, i.e. a min-time and a max-time. Basically, the loss-function is simply losing information about differences on the timeline. With a sufficiently large half-life and buffer, we can plot beautiful graphs.
  • live dashboards – a lot of application software is all about keeping humans aware of useful information in real-time. But humans are interrupted, take coffee breaks, eventually need to sleep. In some cases (e.g. for security systems, military intelligence, or operator control of unmanned systems) someone else will need to take over. After returning from a break, or when taking over an intelligence station, there is one very common question: Catch me up. What did I miss? Naturally, nobody has the time to answer such a question in full detail. So it is naturally addressed with exponential decay. Potentially, we could add a ‘rewind’ option (perhaps presented as a scrollbar) to every user-interface, enabling users to quickly observe how systems their state have evolved in their absence, even to get the fast-motion replays.
  • robust extension of stateful systems – there are many models for stateful systems: mutable imperative variables, state machines and their hundred variations, folds and integrals, term rewriting, process calculi, actors model, relational databases, object databases, and so on. Unfortunately, most state models indirectly lead to software systems that are fragile under common conditions: maintenance, upgrade, extension, disruption. Software is specialized to maintain only the state that it needs. But those needs change. Software is developed with expectation of reliable messaging, observing every event. But then it is hibernated, frozen for debugging, disconnected. Ensuring state is non-local and observations are eventless will help a great deal, but we also want stateful extensions to (metaphorically) hit the ground running. A useful technique is to model an extension’s state as a function of the input history of the parent system, reducing the burden for specialized state per extension. Of course, maintaining and processing the entire history of inputs would be an impractical burden. But if we also address disruption tolerance, the extensions should have little trouble handling the approximate history reported through exponential decay.
  • machine learning – An interesting goal for a system that maintains history with exponential decay is to recover the complete history, or at least a good re-enactment of it. In this case, our loss function would have a goal of compressing the interesting and useful bits from the two frames into one frame, i.e. such that we can recover both frames. By ‘interesting’, I mean that you would not have predicted the value (and thus cannot recover it without recording it). By ‘useful’, I mean that it has predictive value noise, i.e. information that is difficult to compress and has low utility. Access to context (e.g. before and after frames) will be essential for computing what information is predictable in context. I believe that developing a good loss function by hand is infeasible, that we humans won’t be able to explain to a computer which details are useful, and which are predictable. But this is a suitable task for machine learning. In particular, it’s good for unsupervised machine learning (because we can easily test our ability to recover frames), and it’s good for deep machine learning (because our loss function will often be combining frames that have been combined before, possibly at different knowledge depths, thus necessitating multiple ‘layers’ of semantic knowledge and implying cross-layer training). If we develop it carefully, the resulting system will also support both classification (of objects AND actions), and what if? questions (via fictionalizing the context). (By my estimate, machine learning and exponential decay should be very symbiotic, with the history providing the context necessary for intelligent deductions.)

Exponential decay is well aligned with real world evidence models. That is, the real world records a lot of evidence for recent events, and progressively less evidence for past events. Since the amount of ‘state’ in the world is finite and does not grow over time (modulo occasional meteoric impact), the evidence must decay exponentially. (It just doesn’t decay at a uniform exponential rate.) I believe it is also well-aligned with intelligence in animals and humans, since the state and computational resources of a human brain is finite and bounded.

Implementing Exponential Decay of History

Exponential decay of history is one of those rare, great, simple ideas that could revolutionize software development.

Unfortunately, my prior article made it more complicated than it needed to be. In this article, I describe a simpler implementation here, with the following improved qualities:

  • The state of the history can be modeled as a simple list (instead of a stack of ring-buffers). Manipulations and configuration are simple and uniform.
  • The half-life can be tuned arbitrarily. Users can provide it as a natural number, which is reduced to a pseudo-random value from the exponential distribution that indicates which of frames should be collapsed (if any) when adding a new frame.
  • Because frames are collapsed in a probabilistic manner, the resulting model is robust against temporal aliasing. (The prior design could lose valuable information if a condition naturally aligns with periodic loss events. The new design will suffer aliasing only if it exists in the initial input, i.e. based on sensor framerates.)

Warning: The following model is non-type-checked pseudo-Haskell. It’s also a pure model for history, which might not be the best option if dealing with very large values or supporting persistence.

 -- a little context...
import qualified System.Random (StdGen)
import qualified Control.Exception (assert)

type RNG = StdGen
exponentialDist :: Integer -> RNG -> (Integer,RNG)
exponentialDist halfLife rg  = (irg',index) where
    -- implementation assumed

-- pure history with support for stateful context 
data History s a = History
    { h_rand :: !RNG -- pure random numbers
    , h_loss :: !(HLossFn s a) -- see below!
    , h_hist :: ![a] -- the frame buffer 
    , h_state :: !s -- extra state and context
    , h_halflife :: !Integer -- approximate frame count to lose 1/2 info
    , h_maxlen :: !Integer -- how much history to keep?
    }

-- The Loss-function for a history:
--
--    state -> lowerFrames -> upperFrames 
--          -> aNewer -> aOlder -> (state', aMerged)
-- 
-- This loss function gets access to the historical context 
-- (state, lower and upper frames) along with the two frames it
-- is to smash together. It outputs an updated state and a new
-- combined frame. (I.e. the loss function cannot affect the
-- surrounding frames, but may influence state.)
--
--   state: arbitrary, user-defined, but really should have bounded
--      space. Might contain ML models, or recursively more history.
--   lowerFrames: Ordered from latest to earliest (i.e. list zipper style)
--      all younger than aNewer
--   upperFrames: Ordered from earliest to latest; all older than aOlder
--   aNewer: the younger of the two frames being merged
--   aOlder: the older of the two frames being merged
--   state': the updated state.
--   aMerged: the result of combining. 
--
type HLossFn s a = s -> [a] -> [a] -> a -> a -> (s,a)

-- A history-update will always add the given 'a' to the front
-- of the history. Then, it will typically merge two frames 
-- that are later in the history. Though, if the history is
-- smaller than its max length, it may increase its size.
historyUpdate :: a -> History s a -> -> History s a
historyUpdate aUpd h = 
    -- full implementation elided, but basically:
    --   use exponentialDist, halflife, and rng to obtain an index
    --   reduce selected update index if greater than maxlen 
    --   add the aUpd element to the front of the list. (aUpd:h_hist h)
    --   unzip and apply loss function to the elements at index and index+1...
    --     (unless there is no index+1 element)
    --   build and return the updated history 
    --     new hist (~ reverse lower ++ [aMerged] ++ upper ... but efficiently!)
    --     new state
    --     new rng

This is a very simple model. I would not be surprised if other people have developed something similar on their own, though I have not found references and exponential decay was novel to me when I developed it. I have already found applications for exponential decay of history in my day job, and I expect it will become one of a few cornerstone state models in Sirea.

I encourage you to find applications for this state model, to make use of it in practice, to share and blog your own experiences.

Posted in Language Design | 5 Comments

Sirea RDP Progress Update: Demand Monitors are Working

My implementation of Reactive Demand Programming (RDP), Sirea, is gradually approaching a usable state. After vacation, I sat down and in just a couple programming sessions I got demand monitors into a working order. The implementation of demand monitors further provides useful components for rapid, robust integration of a variety of IO resources with RDP.

A demand monitor is a resource modeled as a pair of behaviors (respectively called ‘demand’ and ‘monitor’) that are implicitly coupled through a shared-state side effect. Signals sent to the demand facet are collected into a list then projected to the monitor facet. That is, a set of signals becomes a signal of sets (`(Ord a) => [Sig a] -> Sig [a]`). Demand monitors support many communication patterns: one-to-many communication (by sharing just the monitor facet), many-to-one communication (by sharing just the demand facet), and many-to-many communication (by sharing both).

Demand monitors are among the simplest useful resources in RDP systems, and exhibit many features that (collectively) distinguish RDP from other reactive or dataflow models. Demand monitors support implicit bi-directional communication, logical concurrency, anticipation, and open extension. Understanding demand monitors can help with grokking all of RDP’s effects model, which is why I have said demand monitors the heart and soul of RDP.

In Sirea, demand monitors are accessed as an ambient capability, uniquely identified by data type, partition, a string.

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
type DemandMonitor b p e z = (b (S p e) (S p ()), b (S p ()) (S p z))
class HasDemandMonitor b p where 
    demandMonitor :: (Typeable e, Ord e) => String -> DemandMonitor b p e [e]
instance (Partition p) => HasDemandMonitor (BCX w) p

BCX is a concrete behavior implementation that has access to a reified ‘global’ resource environment. While RDP is designed for object capability model, Sirea favors ambient authority to better align with Haskell’s FFI and module system. The BCX instance of HasDemandMonitor will locate a unique demand monitor resource for each unique (typeOf e, typeOf p, String) triple. This enables easy sharing between independent modules, while also supporting modular encapsulation (e.g. with simple use of newtype).

A toy application with demand monitor might be to receive a set of numbers from behaviors representing concurrent agents, then print them to the console.

test :: BCX w (S P0 ()) (S P0 ())
test = injectNumbers |*| printResult 
   where dm = demandMonitor "test"
         injectNumbers = inject (3 :: Int) |*| inject 7 |*| inject 4 
                     |*| inject 5 |*| inject 4
         inject n = bconst n >>> fst dm
         printResult = snd dm >>> bprint show
main = runSireaApp test -- prints [3,4,5,7] to console

(Aside: `|*| :: b x y -> b x z -> b x x` is concurrent composition, ignoring response from both behaviors. It’s useful for modeling concurrent processes or independent agents. It is easily defined in terms of bdup, bfirst, and bsnd.)

Demand monitors are not complicated, but still present several implementation challenges:

  1. Robust handling of cycles, e.g. progress and predictable performance guarantees. (Cycles are generally of the form `monitor >>> process >>> demand`.)
  2. Garbage collection of cached signal data, especially when detaching
  3. Optimal update-ordering to avoid rework within a step
  4. Efficient processing of cycles; minimizing downstream exposure of intermediate data

Cycles may be robustly broken by shifting the monitor updates into the next partition step. Doing so ensures predictable performance and preserves snapshot consistency (per demand monitor). Optimal update ordering uses the ln_touch mechanism to ensure all updates are received at each locus before processing takes place. For GC, I extended my architecture with an ability to schedule events on ‘heartbeats’, which are provided by the main thread at 10-20Hz.

Unfortunately, I was unable to accomplish the fourth point. Efficient processing of cycles requires breaking them at a minimal set of locations. Once all the cycles are broken, the remaining demand monitors can deliver their updates immediately. Doing so reduces exposure to intermediate values, and generally reduces downstream rework. But breaking cycles precisely requires knowledge of where the cycles are at – knowledge that I cannot obtain due to opaque processing and dynamic behaviors.

So for now, demand monitors in Sirea are working but their performance does not compose or scale very well. I may later need something similar to ln_touch just for detecting cycles, or some sort of static analysis.

An interesting property of cyclic feedback is that it’s also capable of representing volatile state:

tstCycle = monitor >>> bdelay 0.2 >>> addOne 
                   >>> bprint show >>> demand
  where addOne ls = if (null ls) then (0::Int) else (succ (maximum ls))
        (demand,monitor) = demandMonitor "tstCycle"

Here, tstCycle will, five times a second, add one to the highest monitored value and push it back into the demand set. After thirty seconds, the value will be 150, assuming no other influence or interference. This is essentially a model of delay line memory. Use of cyclic feedback to model state is not efficient, stable, robust, or recommended – but does serve as a nice demonstration of why RDP is essentially stateful despite having no local state.

The above tstCycle will actually aggregate memory – not by leaking it, rather by computing / anticipating too far ahead. I’ve developed a behavior `bfchoke` (in Sirea.Utility) to address the issue of anticipating too far ahead, but I’m still contemplating whether I should build something like that into the demand monitors.

(Aside: As an exercise, contemplate tstCycle and understand why it is spatially idempotent – e.g. why redundant computation of tstCycle at the same logical latency will not change the observed effects. By nature, all RDP behaviors are spatially idempotent. This is a useful feature: it enables refactoring or elimination of common sub-expressions, despite side-effects.)

Regular demandMonitor requires an `Ord` constraint, which ensures safe usage: the output list is ordered and without duplicates, representing a set of values. Unfortunately, this hinders use of opaque data types, such as dynamic behaviors. To address this, I also provide unsafeDemandListMonitor, which is just like demandMonitor except without the ordering or duplicate eliminations. This takes more discipline to use safely, i.e. to respect RDP’s invariants.

What’s Next?

The components used for demand monitors are useful for a few IO integration support concepts, i.e. to bridge RDP with Haskell IO. For example, variables that are automatically published to multiple observers, or that automatically gather signals from multiple sources. But that won’t take long (I’m mostly done already, after just a few hours on it). After that, in the short term I plan to implement a simplistic filesystem adapter, maybe a GLFW adapter (basic user input and visual output), and a few state models.

Over the last week or so, I’ve also been working on a more wholistic API user interfaces that aligns well with design principles and features of RDP. (E.g. RDP seems very well suited for variations on immediate-mode GUIs.) I’m leaning heavily towards a reactive application server concept. I would be targeting an abstract client API (also RDP based), and supporting occasionally offline systems but also runtime update while connected. I plan to write more on the subject in a future article.

Posted in Reactive Demand Programming, Open Systems Programming, Concurrency, State | 1 Comment

Stateless Sound

Most audio abstractions involve relative start and some finite bound (end time). That is, we might play(sound) starting at some given instant, and we expect the sound to continue playing until finished. The representation of sound may be stateless (e.g. an MP3 or WAV file). However, processing of the sound event imposes a state requirement on the client or server: it is necessary to remember when the sound started playing, or maintain a cursor to track progress if streaming the sound over time. Even sheet music has a control-flow bias, whereby some interpreter or human is to progress through the ‘code’ and emit sounds.

While the state burden is not severe, it seems a poor fit for many interesting audio applications. For example, I am interested in transforming spatial representations into sound – e.g. obtaining facial descriptors from a video camera at the door and producing a jingle unique to each person, or turning street graffiti, clouds, traffic, and other features into sounds that vary in space (observable via AR glasses or a phone). Broadly, I would like to transform graphs, spreadsheets, images, etc. into sound or music. I would also like to associate sound with live documents and zoomable user interfaces. Many applications suggest a ‘continuous’ notion of sound that lacks a clear beginning or end.

An alternative metaphor to playing sounds is the idea of tuning in to a sound (like a radio), or perhaps amplifying sounds that are already part of our environment. In this abstraction, the interpreter is passive, needing no more state than its own progression through time. This metaphor seems a good fit for declarative, spatial, or reactive representations of sound.

By stateless sound, I mean an audio abstraction and API that achieves at least the following criteria:

  1. does not require the observer or source to maintain any per-sound state
  2. does not require computing a history to determine what sounds are audible at a given instant (i.e. sublinear in time)
  3. is sufficiently expressive to support a broad spectrum of sound applications (e.g. music, voice, environment, game events)

Note that a sound source or observer is permitted to be stateful. One might model a microphone as a sound source. I’ve already expressed interest in modeling sounds as ‘views’ of stateful or time-varying spatial data resources (such as spreadsheets or video). But use of state should be mostly orthogonal to sound, and unnecessary for rich expression and demonstration of music, voice, and other sound information.

I say “mostly” orthogonal because dynamic sounds conflict with some transforms on sound. For example, we cannot arbitrarily attenuate, compress, time-shift, or reverse a dynamic sound source. I haven’t thoroughly explored these issues. It may prove acceptable to compress, attenuate, and shift sounds in some non-arbitrary, limited or balanced manner. Or it may prove wiser to separate these operations into a higher layer. Stateful sources also introduce concerns for stability of composition and transforms. In any case, potential for stateful sound sources will impact design of any audio abstraction and API.

Even with such constraints, stateless sound can be a flexible function of time. A notion similar to play(sound), where the sound uses relative time internally, can be expressed in the stateless model by specifying it on an explicit timeline. Procedural generation is compatible with stateless sound so long as the current sound can be quickly indexed at arbitrary times, which at least allows Perlin noise and simple cyclic models.

An interesting possibility is to insist on an extra constraint, that stateless sound eschews mention of any absolute time. In that case, static sounds will always be cyclic, as would be any sound computed from state that happens to be invariant. Advantages of constraining static sounds to cyclic representation is they’ll be easier to reason about, validate, transform, and compose. Of course, that doesn’t mean much unless we also limit access to lower or non-harmonic frequencies – e.g. insist that every static sound must complete and loop within twelve minutes, or one hour, or one day. Otherwise developers could just use very large epochs to achieve some equivalent to absolute time.

I believe stateless sound models are promising for alleviating developers from some pervasive state management burdens. This could result in more robust, resilient, and reactive systems, and new applications that involve auditory ‘views’ of state. I’ve a few ideas for concrete stateless sound models (i.e. actual types, DSL, interpretations), but I’ll detail those in a later article if I eventually pick something to implement. An open question remains for whether it overly burdens the sound providers and artists, or whether that, too, can be conveniently addressed by simple abstraction. It may be that stateless sound encourages more extensible and reusable designs, e.g. representing active sound-events in an intermediate log rather than encapsulating ‘play’ commands deep in a program.

Motivations: I have an idle (lurk-level) hobby interest in procedural generation of sound and music, and ideas for audio applications that I’d like to develop (but for which I never seem to find the time). I am also developing a new reactive paradigm that eschews events and local state. I’m far enough along to begin thinking about problems like how to provide an audio API for my paradigm that will be convenient, expressive, predictable, interactive, and efficient in its adaptation to an existing audio API (e.g. the JACK Audio Connection Kit). The stateless sound concept seems it could be an effective fit for my paradigm.

Posted in Stability, State, UserInterface | Leave a comment