The term "atom" is used here in its original Greek meaning: ἄτομος (átomos) - "indivisible." These computational primitives are truly atomic because:
- Indivisibility: They cannot be decomposed into smaller computational units within the Transient framework
- Primitiveness: They represent the most fundamental operations for computational movement
- Irreducibility: No simpler operations can achieve the same movement capabilities
- Compositionality: All complex computational patterns are built from combinations of these atoms
Transient represents a paradigm shift in software composition - it provides a complete algebra for orchestrating computation across the entire software universe. At its core are two complementary families of "computational atoms" that work in perfect symmetry.
Transport atoms move computations between execution contexts:
abduce: Thread tunneling - moves computations between threadsteleport: Node tunneling - moves computations between nodes/machinesreact: Framework tunneling - moves computations between frameworks (web, GUI, etc.)wormhole: Connection tunneling - establishes persistent connections for computation movement
These atoms transport the continuation state of computations, allowing them to resume execution in different contexts.
Injection atoms move data into the computation stream:
for: Sequential value injection - injects values one by onechoose: Parallel value injection - injects values concurrentlyasync: IO computation injection - injects IO actions into TransientwaitEvents: Async result injection - injects results of IO computations
These atoms introduce concrete values and results into the stream of computation.
Transport Atoms: Injection Atoms:
--------------- ---------------
abduce (threads) for (sequential values)
teleport (nodes) choose (parallel values)
react (frameworks) async (IO computations)
wormhole (connections) waitEvents (async results)
This symmetry creates a complete system where:
- Transport atoms move computations through space (execution contexts)
- Injection atoms move data through time (computation stream)
The transport/injection duality mirrors quantum mechanics:
- Transport atoms are like quantum teleportation - moving quantum state between locations
- Injection atoms are like quantum measurement - collapsing possibilities into concrete values
Just as quantum mechanics handles both state movement and measurement, Transient handles both computation movement and data injection.
Together, these atoms form a complete algebra for computational orchestration:
- Thread-level movement:
abduce+for/choose - Node-level movement:
teleport+async/waitEvents - Framework integration:
react+ event injection - Persistent connections:
wormhole+ continuous data flow
This model represents a fundamental shift from:
Traditional programming: Computation happens in fixed locations, data moves between them
Transient programming: Data stays in place, computations move to where the data is
This inversion enables:
- Location transparency: Code doesn't care where it executes
- Resource optimization: Computations move to underutilized resources
- Fault tolerance: Failed computations can be restarted elsewhere
- Scalability: Natural distribution across available resources
- Continuation serialization: Computational state is captured and serialized
- Network protocols: Efficient movement between execution contexts
- Resource management: Cleanup and error handling during movement
- Security: Authentication and encryption for cross-context movement
- Stream processing: Efficient handling of value sequences
- Async integration: Seamless IO operation integration
- Backpressure: Flow control for data injection rates
- Error propagation: Proper handling of failed injections
Transient's atomic model provides a unified framework for software composition that transcends traditional boundaries:
- No more "local vs distributed": The same code works everywhere
- No more "synchronous vs asynchronous": The model handles both naturally
- No more "monolithic vs microservices": Computation granularity becomes fluid
- No more "client vs server": Code moves seamlessly between roles
This represents nothing less than a new foundation for software engineering - one based on the fundamental atoms of computation rather than artificial architectural boundaries.
The atoms of computing - transport and injection - provide a complete, elegant, and powerful model for software composition. By understanding and leveraging these fundamental building blocks, we can create software that truly spans the entire computational universe, moving effortlessly between threads, nodes, frameworks, and time itself.
Each atom represents a single, irreducible operation:
abduce: Fundamental thread forking - cannot be implemented with simpler thread operationsteleport: Atomic node-to-node computation movement - no smaller network operation existsreact: Minimal framework integration primitive - the smallest bridge to external systemsfor/choose: Primitive value injection - no simpler way to introduce data into streamsasync/waitEvents: Atomic IO integration - the most basic IO-to-computation bridge
While these atoms compose to form complex patterns, they themselves cannot be decomposed:
-- Complex pattern composed from atoms
distributedProcessing = do
data <- gatherdata -- generate a list or a stream of lists
result <- runAt workerNode $ threads 10 $ do -- execute with 10 threads in the remote node
d <- for data -- unpack each list in a stream of elements
async process -- the 10 thredads generate a stream of "result"
liftIO $ print result -- back in the calling node. Print each result
where
runat node todo=
wormhole node $ do
teleport
r <- todo
teleport
return r
-- But none of these can be broken down further:
-- teleport ≠ simpler network operations
-- abduce ≠ simpler thread operations
-- for ≠ simpler iteration constructsThese operations represent the boundary where Transient's computational model meets the underlying execution environment. They are the "primitive operations" that the framework provides for interacting with:
- Thread schedulers (
abduce) - Network stacks (
teleport,wormhole) - IO subsystems (
async,waitEvents) - External frameworks (
react) - Data streams (
for,choose)
This is not just another programming paradigm - it's a fundamental rethinking of what computation means and how we should structure our software systems for the age of distributed, asynchronous, and ubiquitous computing.
Beyond movement and injection, a third family of atoms facilitates communication within a single process, acting as the glue between different parts of the same program.
EVars: Local, explicit communication channels. They function like a bulletin board where one part of the code can post messages (writeEVar) and other parts can subscribe to receive them (readEVar). They require an explicit reference to be passed between components.Mailboxes: Global, implicit communication channels. They extendEVarsby creating a global registry where components can publish (putMailbox) and subscribe (getMailbox) to messages based on their data type, without needing an explicit reference.
These atoms are analogous to how callbacks work between different frameworks, but they are integrated into the same unified, composable transient model, allowing for decoupled communication.
The concept of a "Grand Unified Theory" (GUT) in physics seeks to unite the fundamental forces of nature into a single theoretical framework. transient applies this same ambition to the world of computing.
Traditionally, different forms of computation have been treated as separate domains, each with its own distinct tools, patterns, and complexities:
- Synchronous vs. Asynchronous
- Single-threaded vs. Multi-threaded (Threading)
- Parallelism
- Local vs. Distributed
- Streaming
transient unifies all these forms under a single, coherent algebraic model. The atomic primitives do not distinguish between these domains. abduce moves a computation to another thread, and teleport moves it to another machine, but the code and the composition logic remain identical. A stream of events from waitEvents is composed with the <|> operator in the same way as a choice between two user options.
This unification means the developer can write business logic at the highest level of abstraction, describing the what, while the framework seamlessly handles the how across all these computational paradigms.
Any algebraic formula that you can imagine usign binary operatios either mathematical boolean, from set theory, relational or watherver you invent can be performed among entire computations with this simple translation:
myOperatorT= myOperator <$> term1 <*> term2
where myOperator is your operator for numbers strings or your own data types and myOperatorT will be your operator for composing computations that returns these same data types. If one of the terms perform streaming, the combination will produce a stream of results instead of a single result.
The golden age of algorithmic simplicity was based on linearity: a single, predictable flow of execution where I/O operations were interleaved. While this model was easy to reason about, it came at the cost of blocking and was shattered by the realities of modern computing:
- Multiple Input Streams: The introduction of the mouse, and later network communications, created multiple, simultaneous sources of events that broke the single-threaded model.
- Threading and Parallelism: Introduced to handle concurrency, they fragmented the execution flow into multiple, hard-to-coordinate timelines.
- Inversion of Control: Asynchronous requests and callbacks between objects and frameworks turned the control flow inside out. The logic was no longer a straight line but a series of fragmented reactions to events.
transient restores this lost linearity, but without the original sin of blocking. It does so by "sewing" together these fragmented lines of computation. The atoms of computing (abduce, teleport, react, waitEvents, etc.) are the needles and thread that bridge the gaps between threads, callbacks, network requests, and event streams.
They allow the developer to once again write code that follows the natural, high-level description of a process, as if it were a single, linear flow. The immense complexity of managing the underlying concurrency, asynchrony, and distribution is abstracted away, returning the power of simple, composable, and non-blocking linearity to the programmer.