Summary
I've been intrigued by closures since I taught introductory computer science from SICP at UC Davis in 1988. This post dives into a little programming language theory to trace a line from closures to objects to actors to picos, showing what each step adds. A pico is a closure that has been made to answer for itself in the world, with persistent state, a durable identity, and relationships it can carry when it moves.
I've been intrigued by closures ever since I taught an introductory computer science course from Structure and Interpretation of Computer Programs (SICP) at UC Davis in 1988. Watching students realize that a function plus the environment it was born in was enough to build objects, with no classes and no special syntax, was one of the great pleasures of teaching that course. I've followed the idea around ever since and used it wherever I could. When I built picos, I described each one as a closure over services and data. I've also described picos as implementing the actor model of distributed computing. But as this week's post on moving picos between engines shows, they're something more than that.
Closures in SICP
A closure is a procedure bundled with the environment in which it was created. When you call it, the body runs against bindings that were captured at creation time and that nothing outside the closure can see. Chapter 3 of SICP uses this to build a bank account:
(define (make-account balance)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
(define (deposit amount)
(set! balance (+ balance amount))
balance)
(define (dispatch m)
(cond ((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
(else (error "Unknown request" m))))
dispatch)
(define acc (make-account 100))
((acc 'withdraw) 50) ; => 50
The variable balance lives in the environment that make-account created, and the only way to reach it is through dispatch. Each call to make-account produces a new environment, so every account has its own private balance. SICP calls this style message passing: you don't operate on the account's data; you send the account a message and it decides what to do.
SICP is also careful about what this costs. Once a closure holds mutable state, two accounts that start with the same balance are no longer interchangeable, and the book spends a section on sameness and change to work through what that means. With state comes identity. That observation turns out to be the thread that runs all the way to picos.
Closures and Objects
The account above is an object in every way that matters: encapsulated state, behavior bound to that state, and an interface defined by the messages it accepts. make-account plays the role of a class, and dispatch is a hand-built method table. There's an old programming koan that captures the equivalence: closures are a poor man's objects, and objects are a poor man's closures. Each can be built from the other.
What object-oriented languages add is vocabulary and machinery. Classes give the constructor a name and a place to live; inheritance lets one dispatch table extend another; method syntax spares you from writing the cond yourself. Those are real conveniences, but they don't change the underlying model. An object, like a closure, lives in one address space, is named by a pointer that means nothing outside that heap, and is invoked by a synchronous call that runs on the caller's thread. When the process ends, so does the object.
Actors
The actor model, introduced by Carl Hewitt in 1973 and formalized by Gul Agha in the 1980s, takes message passing literally. An actor has private state and a mailbox. It processes messages one at a time, and in response to a message it can do three things: send messages to actors it knows about, create new actors, and decide how it will handle the next message. Messages are asynchronous; the sender doesn't wait, and there is no shared memory between actors.
Agha's framing of an actor as a closure with a mailbox is the halfway point between the SICP account and a pico. The dispatch is still there, but the call has become a message in a queue, and the one-at-a-time rule replaces locks as the way an actor protects its state. Actors also introduce something closures and objects lack: an address that is useful only to those who have been given it. Knowing an actor's address is the capability to talk to it. That's a short step from a reference to a relationship.
Picos
A pico is an actor, and you can easily translate the SICP account to KRL. Here's the withdraw written as a KRL rule:
rule withdraw {
select when account withdrawal
pre {
amount = event:attrs{"amount"}
}
if ent:balance >= amount then noop()
fired {
ent:balance := ent:balance - amount
raise account event "balance_updated" attributes {
"balance" : ent:balance
}
} else {
raise account event "insufficient_funds" attributes {
"balance" : ent:balance,
"amount" : amount
}
}
}
The entity variable ent:balance is the captured environment, the select when clause is the dispatch, and an account:withdrawal event is the message. Each pico has its own event queue and retrieves events from it one at a time, just as an actor drains its mailbox. A pico usually has several rulesets installed, each with its own entity variables, so it's more precisely a set of closures over separate environments that share one identity and one event queue.
Where picos depart from closures, objects, and most actor systems is that they insist on surviving. A pico's state is persistent by default, so it outlives the process that happens to be running it. Picos are internet-first; its identity is a DID that anyone on the network can resolve, not a pointer or a runtime address. Its references to other picos are channels and peer DIDs, each named, governed by policy, and revocable, rather than raw pointers with unlimited authority. And its code can change over its lifetime as rulesets are installed and removed. Each of those properties follows from the first: once state has to outlive the process, it has to be persistent; once a persistent entity can be reached from anywhere, its identity has to be a name; once that name can be handed to strangers, the reference has to carry policy.
Those same choices are what make a pico movable. Nobody moves closures between machines, because the captured environment is opaque and full of pointers into the local heap; the mobile-code systems that tried, like Emerald, Obliq, and Telescript, had to invent restrictions to make it work. A pico never captures anything that can't follow it. Entity variables are plain data, rulesets are fetched by URL, and every reference to another pico is a name. Exporting a pico is closure serialization, importing it is rehydration, and the ECI remapping on import is pointer swizzling under a different name.
Side by Side
The table lines up the four models on the properties that change as you move from one to the next. Every cell could be argued at length, and particular languages and actor systems blur some of these lines, but the rows are the properties that matter when you want software to last longer than the process running it.
| Closure | Object | Actor | Pico | |
|---|---|---|---|---|
| State | Captured lexical environment | Instance variables | Private local state | Entity variables, one set per ruleset |
| Identity | Pointer in one heap | Reference in one heap | Mailbox address | DID resolvable on the internet |
| Invocation | Synchronous call | Synchronous method call | Asynchronous message | Asynchronous event; synchronous query |
| Concurrency | Caller's thread | Caller's thread; shared memory possible | One message at a time | One event at a time; no shared memory |
| Lifetime | Until garbage collected | Until garbage collected | Usually the life of the runtime | Persistent; survives restarts |
| Behavior change | Fixed at creation | Fixed by class | Chooses behavior for next message | Rulesets installed and removed over its life |
| References to others | Captured variables | Object references | Actor addresses | Channels and peer DIDs with policy |
| Mobility | Bound to its heap | Bound to its heap | Location-transparent in some systems | Moves between engines with relationships intact |
Read left to right, each column keeps what the one before it had and adds something. Objects give closures a vocabulary. Actors give objects asynchrony and isolation. Picos give actors persistence, a durable identity, and relationships they can carry with them. SICP's lesson about sameness and change was that state brings identity with it; picos take that seriously enough to make the identity cryptographic and the state portable.
A closure in 1988 was a beautiful way to think about computation. A pico is a closure that has been made to answer for itself in the world, one that can belong to a person rather than being tied to the machine it runs on.
Photo Credit: Closure to Pico from ChatGPT (public domain)





