Chorex
Choreographic programming in Elixir
Install / Use
/learn @utahplt/ChorexREADME
Chorex - Choreographic Programming in Elixir
Synopsis
Note: this documentation is current as of 2025-09-10, The project is evolving rapidly, so this README may occasionally get out-of-sync with what the project can do.
Describe the choreography in a module with the defchor macro:
defmodule TestChor do
defchor [Buyer, Seller] do
def run(Buyer.(book_title)) do
Buyer.(book_title) ~> Seller.(b)
Seller.get_price(b) ~> Buyer.(p)
Buyer.(p)
end
end
end
Implement the actors:
defmodule MyBuyer do
use TestChor.Chorex, Buyer
end
defmodule MySeller do
use TestChor.Chorex, Seller
def get_price("Das Glasperlenspiel"), do: 42
def get_price("A Tale of Two Cities"), do: 16
end
Elsewhere in your program:
Chorex.start(TestChor.Chorex, %{Seller => MySeller, Buyer => MyBuyer}, ["Das Glasperlenspiel"])
receive do
{:chorex_return, Buyer, val} ->
IO.puts("Got #{val}") # prints "Got 42"
end
Chorex.start(TestChor.Chorex, %{Seller => MySeller, Buyer => MyBuyer}, ["A Tale of Two Cities"])
receive do
{:chorex_return, Buyer, val} ->
IO.puts("Got #{val}") # prints "Got 16"
end
Description
Chorex is a library for choreographic programming in Elixir. Choreographic programming is a programming paradigm where you specify the interactions between different entities in a concurrent system in one global view, and then extract implementations for each of those actors. See § Bibliography for references on choreographic programming in general.
Installation
Chorex is available on Hex.pm. Install by including the following in your mix.exs file under the deps list:
def deps do
[
...,
{:chorex, "~> 0.9.0"},
...
]
end
You can install development versions of Chorex directly from GitHub like so:
def deps do
[
...,
{:chorex, github: "utahplt/chorex"},
...
]
end
Add Chorex.Registry to your application setup:
# part of application startup; e.g. in a Phoenix application this
# would be in MyApp.Application located at lib/my_app/application.ex
children = [
{Registry, name: Chorex.Registry, keys: :unique}
]
Note that this is experimental software and stuff will break. Please don't rely on this for anything production-grade. Not yet at least.
What is a choreography?
A choreography is a birds-eye view of an interaction between nodes in a distributed system. You have some set of actors—in Elixir parlance processes—that exchange messages while also running some local computation—i.e. functions that don't rely on talking to other nodes in the system.
Lindsey Kuper's research group has put together a delightful zine explaining choreographic programming. Check that out if you are new to choreographies.
At a high-level, Chorex lets you build choreographies to describe different interactions between components of your system. Chorex focuses on the communication flow; you still implement the computation that runs locally on each node, but you don't have to worry about writing sends between nodes.
Once you have a choreography, you can instantiate it any number of times as you like. You might want, for example, to have one choreography describing how a user actor would communicate to create an account on a system, and then another choreography for how an existing user would log in with previously established credentials.
Choreography syntax
Chorex introduces some new Elixir syntax for choreographies. Here's a breakdown of how it works.
Start by creating a module to hold the choreography, say import Chorex, and add a defchor block:
defmodule MyCoolChoreography do
import Chorex
defchor [Actor1, Actor2, ...] do
...choreography body...
end
end
(Note: in addition to the choreography definition here, you will need to make a module for each actor. We'll focus on the special syntax of the defchor block in this section, but later you'll see how to built a module for each of the Actor1, Actor2, etc.)
The defchor macro wraps a choreography and translates it into core Elixir code. You give defchor a list of actors, specified as if they were module names, and then a do block wraps the choreography body.
The body of the choreography is a set of functions. One function named run must be present: this serves as the entry point into the choreography. The arguments to run come from the third argument to the Chorex.start function and are how you typically get values into an instantiation of a choreography. (More on Chorex.start and function parameters in a minute.)
defchor [Actor1, Actor2, ...] do
def some_func(...) do
...
end
def run() do
...
end
end
Message passing expressions
Inside the body of functions you can write message delivery expressions. Examples:
Actor1.(var1) ~> Actor2.(var2_a)
Actor1.func_1() ~> Actor2.(var2_b)
Actor1.func_2(var1_a, var1_b) ~> Actor2.(var2_c)
Actor1.(var1_a + var1_b) ~> Actor2.(var2_c)
Formal syntax:
msg_delivery ::= $local_exp ~> $actor.($pat)
local_exp ::= $actor.($pat)
| $actor.$func($exp, ...)
| $actor.($exp)
actor ::= Module name (e.g. Actor)
func ::= Function name (e.g. frobnicate(...))
pat ::= Pattern match expr (e.g. a variable like `foo` or tuples `{:ok, bar}` etc.)
exp ::= Elixir expression (e.g. foo + sum([1, 2, 3]))
The ~> indicates sending a message between actors. The left-hand-side must be Actor1.<something>, where that <something> bit can be one of three things:
- A variable local to Actor1
- A function local to Actor1 (with or without arguments, also all local to Actor1)
- An expression local to Actor1
The right-hand-side must be Actor2.(<pattern>). This means that the left-hand-side will be computed on Actor1 and send to Actor2 where it will be matched against the pattern pattern.
Local expressions
Local expressions are computations that happen on a single node. These computations are isolated from each other—i.e. every location has its own variables. For example, if I say:
defchor [Holmes, Watson] do
def discombobulate(Holmes.(clue)) do
...
end
end
Then inside the body of that function, I can talk about the variable clue which is located on the Holmes node. I can't, for instance, talk about the variable clue on the Watson node.
Holmes.(clue + 1) # fine
Watson.(clue * 2) # error: variable `clue` not defined
I can send the value in Holmes' clue variable to Watson, at which point Watson can do computation with the value:
Holmes.(clue) ~> Watson.(holmes_observes)
if Watson.remember(holmes_observes) do
...
else
...
end
The remember function here will be defined on the the implementation for the Watson actor.
ACHTUNG!! mix format will rewrite Actor1.var1 to Actor1.var1() which is a function call instead of a variable! Wrap variables in parens like Actor1.(var1) if you want to use mix format! This is an unfortunate drawback—suggestions on fixing this would be welcome.
Local functions are not defined as part of the choreography; instead, you implement these in a separate Elixir module. More on that later.
if expressions and knowledge of choice broadcasting
if Actor1.make_decision(), notify: [Actor2] do
...
else
...
end
if expressions are supported. Some actor makes a choice of which branch to go down. It is then crucial that that deciding actor inform all other actors about the choice of branch with the special notify: [Actor2, Actor3, ...] syntax. If this is omitted, all actors will be informed, which may lead to more messages being sent than necessary.
Function syntax
defchor [Alice, Bob] do
def run(Alice.(msg)) do
with Bob.({pub, priv}) <- Bob.gen_key() do
Bob.(pub) ~> Alice.(key)
exchange_message(Alice.encrypt(msg <> "\n love, Alice", key), Bob.(priv))
end
end
def exchange_message(Alice.(enc_msg), Bob.(priv)) do
Alice.(enc_msg) ~> Bob.(enc_msg)
Alice.(:letter_sent)
Bob.decrypt(enc_msg, priv)
end
end
Choreographies support functions and function calls—even recursive ones. Function parameters need to be annotated with the actor they live at, and the arguments when calling the function need to match. Calling a function with the wrong actor will result in the parameter getting nil. E.g. calling exchange_message above like so will not work properly:
exchange_message(Bob.(msg), Alice.(priv))
(and not just because the variables are wrong—the actor names don't match so the parameters won't get the values they need).
Higher-order choreographies
def higher_order_chor(other_chor) do
... other_chor.(...) ...
end
Chorex supports higher-order choreographies. This means you can pass the functions defined inside the defchor block around as you would with functions. Higher-order choreographic functions don't get an actor prefix and you call them as you would a function bound to a variable, like so:
defchor [Actor, OtherActor] do
def higher_order_chor(other_chor) do
... other_chor.(...) ...
end
def some_local_chor(Actor.(var_name)) do
Actor.(var_name) ~> OtherActor.(other_var)
OtherActor.(other_var)
end
def run() do
higher_order_chor(@some_local_chor/1)
end
end
Note that when referring to the function, you must use the @func_name/3 syntax—the Chorex compiler notices th
Related Skills
node-connect
339.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
339.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.9kCommit, push, and open a PR
