Serguei Filimonov

Pseudotime series: What is Pseudotime?

...what McCarthy was saying...look, what we are doing on the computer is simulating models; and the thing we're basically doing wrong is we're letting the CPU tell us what time it is. What we wanna do is: we should simulate time also. We'll use pseudotime. -- Alan Kay ([REBASE24] Interview)

Databases today do a very poor job with time. You know, before we had computers, we had the terms "memory" and "records" and they meant highly durable things. "Memory" meant remembering things, and "records" were things you kept, not thing you kept writing over. -- Rich Hickey (The Datomic Architecture and Data Model)

Introduction

What if "time" is an "application-specific" concept? In our software systems, things change over time, entities we model get "updated". How valuable to you is access to the past? Is it valuable to constrain what is and isn't allowed to be "simultaneous"? And if so, would all applications use the same constraints?

Lets explore what it's like to work with software that explicitly models it's own sense of "time" - a "pseudotime".

What is "pseudotime"?

If you google "pseudotime" or even "Alan Kay pseudotime", the search results are awful. But that doesn't mean the concept is too obscure. By zooming up and broadening our concept of pseudotime, we don't have to limit ourselves to the authoritative, but niche, definitions. We'll take a stab at defining it in verbally (dictionary style), operationally(how you hold/wield/use it), and via examples (pointing at pseudotime in action).

That said, first, lets gesture towards some of the earliest literature on the topic via the above Alan Kay quote. Seems the earliest discussion of the concept is from John McCarthy, the famous inventor of the LISP programming language. According to Kay's retelling of the concept, McCarthy attached a "time"/"timestamp" to every "variable" that a robot/software/"advice taker" would reason about. He called this "variable+timestamp" combo a "fluent" apparently. We'll take Kay at his word that pseudotime continues the idea McCarthy proposed.

The abstract and mathematical sense in which McCarthy was thinking and writing about these things must allow for "time" to have a range of possible implementations. It doesn't have to be something like 2025-11-05T10:20:50-05:00 (iso8601 timestamp) but could be something like "version/revision number" 1,2,3,4,5,.... We can choose to model "time" how it makes sense for our software system.

My informal definition

To me, these are the properties of what defines pseudotime (Properties are numbered to refer to them in the examples below):

  • (1) Pseudotime "movement"/advancing is caused by change, state change, and/or "events" of the software system or application (possibly scoped to some particular entity or a situation, more on this later). "Real" time that we have from the universe "moves" or advances regardless of whether anything interesting happens. Pseudotime is exclusively caused by entity/state changes. If time didn't change, we can conclude that state didn't change. There may be edgecases where that rule is, technically, violated, but I stand by this being an important property.
  • (2) Pseudotime is discrete, and easy to navigate backwards. This is mostly a consequence of the first property, but worth mentioning. It's less like a floating-point number and more like an integer. Pseudotime increments or "ticks" forward by a clear "increment", which therefore allows us to, for example, trivially calculate a "previous" time. The integer "previous" to 7 is 6. But the "previous" floating point number to 7 is unclear (is it 6.9 or 6.99 or 6.999 ...).
  • (3) The application and it's domain cares to control and distinguish whether a group of state changes happens "together" within a single time "tick" or individually, in series. This ties to the familiar concept of a "transaction" from databases.

Operational definition

To help flesh out the above informal definitions, here's a couple phrases to illustrate using/experiencing pseudotime:

  • (4) When looking at the output of the sofware (e.g. a web page, part of a webpage or some other "query result", etc.) we ask "What pseudotime was this at?" The idea here is that our software latches on to a "moment" of pseudotime, and explores the state "as of" that pseudotime to produce a variety of calculations and outputs. Rich Hickey calls this a stable "basis" for calculations. Even if further change happened to the entities we're interested in, we can often hold on or retain a grasp on the state connected to that "moment" or at least be aware that "things have changed" since our calculation started.
  • (5) We "increment" or "tick" the pseudotime forward when a change happens. This must be hermetic or air-tight to make pseudotime most useful. We'll come back to the this in a further post.

Real world examples

Thinking broadly about pseudotime, simultanaety, and very-careful concurrency (no race conditions allowed), lets see some examples of pseudotime already featured in real applications, even if they're usually not discussed in such terms.

Git

Most discussions of immutability and related topics make the analogy to Git version control system. And for good reasons. Everyone is familiar with the feeling of how useful Git's "access to past revisions" is. At first glance, "time" in Git is a regular old "clock" time. Commits have "timestamps" of when they were created, but that's not the interesting "time" in Git's domain. The interesting "time" in Git, comes from the main thing that we do in Git. We add commits "on top" of other commits.

So, in a branch with 3 commits (each pointing to the one previous one, a "parent"), the answer to "what time is it?" is 3. Because there's been 3 commits. The chain of Git "shas" in the branch is our pseudotime. Lets see how the Git experience matches our pseudotime properties and usages:

  1. new Git shas within a branch only come about when new commits are added. Aside from rare, explicitly empty commits, you can conclude there was a "change" if the branch "sha" changed, especialy if the length of the branch grew
  2. We can navigate to the "previous" pseudotime via the "parent" pointer of the commit.
  3. We very much care to group changes to multiple, separate "files" into 1 commit when appropriate. For example, to maintain an invariant that every commit produces a compiling program that passes it's test suite.
  4. A. When encountering a bug or some app behaviour, we ask "What version are you running?" and the answer is a sha that we can redeem for a full state of the repo at that time.
  5. B. We "increment" or move the time forward by making a commit and adding it to the top of the branch.

Chess (any turn-based game or even a real-time one)

If we see a chess game, mostly in the starting position, except for 2 pawns at e4 and e5, and the knight at e4, what time is it? The answer is 3 . There's been 3 turns made. Next turn would cause 4 to be the new "time". I would suspect many games and videogames have a notion of "tick", a game tick, which denotes "when" the state of the game is in. Lets observe our pseudotime sense in this example:

  1. When you're playing chess, casually, real-world-clock time has little impact on the game. In a sense, time only moves, when players make moves. Competitive chess brings back the sense of "real-time" in addition to an implicit game time ("it's the 3rd turn"). In real-time videogames, many things are defined in terms of game ticks or "animation frames", so if your computer is running slow and framerate drops, the "rules of the game" are oblivious to it, because it follows it's own "ticks" of the game time.
  2. There's a pretty clear sense of "previous" state in chess or turn based games. And even in something like Doom, i have a hunch, debugging the game involves the state of the game becoming incorrect as a result of the changes that happen during 1 "tick"
  3. In chess, there's "atomicity" in moves like the "castle" - 2 pieces, king and rook, move together as 1 "turn". Even the act of one piece taking over the other count as 1 move. I'm no game developer, but my guess is something similar comes up during the game loop.
  4. The "current pseudotime" in games is typically relatively hidden or obscured from us. Though even in chess, you can count the moves for fun or to analyze strategy.
  5. Games are all about "moves", "turns" and "game ticks" / "game loop" (iterations)

Event Sourcing

Similar to the chess example above, if your application has an event-sourced entity, that entity is guided through its changes by events, which form an ever-growing list. The size of the list is the "time". Running through our pseudotime properties/usage:

  1. In event sourcing, the source of application state is the event "list", but it's ubiquitous to stamp the first event with number 1( 2nd with 2, etc.) and refer to the state caused by the even as "version 1" of the entity/aggregate/etc. So this number, the version, the event counter, only increments, advances as a result of new events having happened. So the number of events having happened is our pseudotime
  2. Via integer decrementing we can trivially navigate to the "previous" time, e.g. if theres 17 events, we would look at 16 as the "previous" time and examine the sub-list of the first 16 events to explore this "previous" moment.
  3. In event sourcing, one event could cause a change to multiple properties of an entity, or even change multiple entities within a single aggregate. Much of event sourcing model design involves negotiating the boundaries between entities and how that impacts concurrency controls and the utility of the history.
  4. For debugging and various collaboration implementations, it's quite useful to annotate the outputs of the event sourced application with the version or event count. Since, previous/past states are accessible via "replaying" smaller sub-lists of the event list, we can access those via the pseudotime as a pointer of sorts.
  5. Similar to the games example, each new event causes the "time" to move by increasing the size of the event list.

Fancy pseudotimes

What I like about the above examples of "pseudotime" is that they are accessible and easy to reason about for a lay programmer such as myself. I have to mention though, that if we were to trace the lineage of this concept to the influences Alan Kay provides, there are discussions of "modelling time" that are far more advanced, but are kind of "hidden" from us, day-to-day programmers. It's entirely likely that their definitions of "pseudotime" are much more formal and different from the above. But I would imagine the above properties somehow also manifest themselves in these designs:

  • Perhaps the most impactful application of the 'pseudotime' concept is the 1978 PhD thesis by David P. Reed, wherein chapter 3: "Pseudo-Time and Possibilities" the concept makes the appearance. The paper is commonly cited as an overwhelming influence on MVCC (the concurrency control mechanism in the popular Postgres database and many others). It might even be the case that "2-phase commit" protocol takes some influence from this "NAMOS" thesis.

  • Kay also cites "Tea Time" protocol as having pseudotime at it's core. Not surprising, given David P. Reed and Kay himself supposedly contributed to the protocol design and implementation.

  • Leslie Lamport is also a figure in the pseudotime neighbourhood. Among numerous contributions to the field of concurrency and distributed systems, Lamport created or advanced concepts such as "Vector clocks", "Lamport timestamps", etc. Clearly he's into the "modeling time" idea.

  • Databases like Datomic or XTDB provide a database-wide sense of pseudotime which makes it almost as easy to use as the typical real-world time.

These "fancy" pseudotime ideas are deeply embedded in the tech we build our applications on. In the databases (distributed or not), in concurrency mechanisms (e.g software-transactional memory), and probably other areas.

While these computer science giants are far superior masters of the pseudotime concept, and can apply it to massively-scaled effects via the platforms we build on, i have a hunch theres pseudotime applications that have a place higher up in the “applications” we work on.

Conclusion

I hope I've shown above that "pseudotime" doesn't have to be a niche tool. I'd love to intrigue you with my quest to harness the power of applying relatively even basic "poor man's" implementations of pseudotime to very practical software applications.

In the next post, we'll see a discussion of how to leverage pseudotime to get the most productivity and business value from it. In the future installments, I'll present a few examples of how to implement a pseudotime in typical web application.