https://github.com/user-attachments/assets/9c975b9b-5c17-4d41-8f9b-1015b8b81e28
Pallas is an event sourced, purely functional application platform, called an operating function. Every operation inside an operating function is ACID. Pallas communicates with the outside world through a small set of frozen hardware interfaces and manages internal processes through an Erlang-style actor model.
The platform ships with a minimal bootstrapping language called Sire, but includes an efficient axiomatic IR which can be targeted by mainstream functional languages.
Software is being destroyed by accidental complexity. Our most popular applications require hundreds or thousands of developers to build and maintain. This complexity benefits large corporations who use their dominant capital positions to monopolize and rent seek. This dynamic increases the cost of software, reduces the set of economically viable programs, and disempowers developers and users alike.
This is not an abstract moral problem. Software is the best tool we have for identifying, organizing, and solving societal issues. Instability in software trickles down to every other domain of human activity.
More composable software systems directly result in more software freedom. The more power individual developers wield, the more that power is widely distributed. This is true even if "scrolling through silos" is the highest state of computing.
To increase developer power and software freedom, your entire computer needs to be inspectable and understandable. It should be composable and made of highly generic, unopinionated, easily understood, reusable building blocks. These properties need to be stable regardless of underlying hardware changes and should guarantee a high degree of backward and forward compatibility.
No database code
Serialize anything, running programs included
Parallelism with deterministic replay
Global referentially-transparent content store
Native networking and identity
Formally specified system calls
Extensible language platform
Pallas collapses the distinction between runtime, database, and operating system. The foundation of Pallas is untyped, but conceptually we can say that a Pallas process is a database of type:
type DB = Input -> (Output, DB)
If a user supplies such a function, the Pallas runtime will create a database using a snapshot-and-event-log system. The user can write their programs as if they were keeping their data "in memory", without any need for manual persistence or other forms of cache management.
The recursive part of the type above might seem strange. You can think of it almost as a normal stateful function:
type OtherDB = (State, Input) -> (State, Output)
The difference is that instead of changing the state value, the recursive version would change itself. The current version of the function is the state. In other words: programs can upgrade themselves dynamically. Code can construct code. Because of this, we can put an entire compiler toolchain inside the system and the programs it generates have zero dependencies on the outside world.
Sire is the default programming language of Pallas. It is an applicative, functional language. A Pallas machine consists of a set of persistent processes known as Cogs.
This code is for a simple counter cog that gets the time and increments a value. Counter state is persisted through cog restarts.
The documentation covers the language more fully.
#### demo_count_up <- prelude
:| prelude
;;; Count Up ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
= (countLoop count k)
| trk [{counter is at} count]
| syscall TIME_WHEN
& now
: _ < syscall (TIME_WAIT (inc now))
| countLoop (inc count) k
= (main)
| runCog (countLoop 0)
;;; Example output:
;;; ++ [%trk {2024-08-02T17:05:48.468929096Z}]
;;; ++ [{counter is at} 1]
;;;
;;; ++ [%trk {2024-08-02T17:05:49.470040565Z}]
;;; ++ [{counter is at} 2]
;;;
;;; ++ [%trk {2024-08-02T17:05:50.471186301Z}]
;;; ++ [{counter is at} 3]
#### demo_count_up <- prelude
Inline 'prelude' before 'demo_count_up'.
:| prelude
Import 'prelude.sire'.
= (countLoop count k)
=
declares a top level binding. The function named countLoop
takes a 'count' parameter (and a 'k' parameter that we don't need to worry about now).
| trk [{counter is at} count]
Print out the current count to the console with the trk
(track) function from the standard library. The bar |
is function application. It says "apply the function trk
to the arguments after it."
trk
is a function that takes two arguments:
| syscall TIME_WHEN
& now
Next, we want to do a clock system call for the current time. The syscall
function takes two arguments:
TIME_WHEN
("give me the current system time" in this case).a continuation to use when the syscall completes.
The system call might take a while to complete, but when it does, we'll be woken up and the result of the call will be passed as an argument to the continuation function that was provided to syscall
. We use &
to define the continuation function as a lambda. The lambda takes one argument called now
, which will be bound to the result of the TIME_WHEN
call.
Pallas syscalls are not like those found in GNU C. Each syscall and response is mapped in an array managed by a small internal micro-kernel. The Pallas runtime is responsible for actually interacting with the external system.
: resultOfTimeWait < syscall (TIME_WAIT (inc now))
Now we want to wait 1 second. We'll use this opportunity to show an alternative style that you'll come across often in Pallas code.
We're going to use the TIME_WAIT
syscall. TIME_WAIT
itself takes a single argument - when to stop waiting. We want to wait 1 second, which is the current time plus 1. At this point we are in the body of the continuation and have now
in scope. The inc
function takes a value and returns the result of adding 1 to it. inc
is applied to now
by wrapping both in parentheses.
Also note that rather than using the &
anonymous lambda style, we're now using the col macro, :
("col" as in colon).
On the right side of the <
we're doing the syscall and the result of that call gets bound to resultOfTimeWait
. As with the previous syscall, the next argument is a continuation, which again, is the rest of the code below the col macro.
The col macro is a method of writing continuation-passing style in a way that feels like assignment. It feels as if syscall returns resultOfTimeWait
which can be used in the remainder of the body. Col macro-expands into the same code as the &
version above.
Our goal with TIME_WAIT
was just to wait 1 second. We don't actually use the "result" of the TIME_WAIT
syscall (bound to resultOfTimeWait
). In this case, we could also have bound it to _
to denote this.
| countLoop (inc count) k
After waiting 1 second, we recur and pass an incremented value for count
.
= (main)
| runCog (countLoop 0)
Finally, we'll start a process that will be responsible for running this function and handling any threads or syscalls that are involved. These processes are called "cogs" and are initiated with the runCog
function.
We bind a top-level main
function that will call runCog
. When we pass countLoop
to runCog
as its "job", we also need to provide the starting count of zero.
You have two options: build a dev environment using Nix, or clone the repo and install a binary. The binary is supplied for convenience only and still requires the source code in order to function.
On Ubuntu or Debian-based systems:
sudo apt-get update && sudo apt-get install -y \
libgmp10 \
liblmdb0 \
zlib1g
On MacOS:
brew install gmp lmdb zlib
Your browser may not prompt to download these files, in which case you can use cURL
:
curl -L <URL of your choice here> -o pallas
Make it executable and move it somewhere on your path.
If all went well, you should see this:
$ pallas
Run a Pallas machine
Usage: pallas COMMAND
Pallas
Available options:
-h,--help Show this help text
Available commands:
sire Run a standalone Sire repl.
save Load a sire file and save a seed.
show Print a seed file.
repl Interact with a seed file.
start Resume an idle machine.
boot Boot a machine.
Using Nix is the most straightforward way to install Pallas at this time. If your system doesn't support Nix or if you need further instruction (including instructions for Docker), refer to the documentation.
git clone git@github.com:operating-function/pallas.git
cd pallas
nix develop
This will take some time. Perhaps upwards of 15 minutes, depending on your system.
stack build
$ stack run pallas
Run a Pallas machine
Usage: pallas COMMAND
Pallas
Available options:
-h,--help Show this help text
Available commands:
sire Run a standalone Sire repl.
save Load a sire file and save a seed.
show Print a seed file.
repl Interact with a seed file.
start Resume an idle machine.
boot Boot a machine.
Navigate to the root of this repository and run the commands below to see a simple demonstration of running a Pallas machine.
(The following demo uses /tmp
as the location on the host filesystem
to create a directory named counter
to hold the event log of the machine.
Feel free to use a different directory if you'd like.)
pallas boot /tmp/counter sire/demo_count_up.sire
pallas start /tmp/counter
Take note of the final counter value and then Ctrl-C
to kill the machine.
++ [%trk {2020-09-21T10:15:00.729241178Z}]
++ [{counter is at}=7]
Now run pallas start /tmp/counter
again. The counter picks up where it
left off. You'll notice that there is no explicit saving or
writing to disk or a database. You get persistence for free by writing
application code.
(For more on how Pallas machines work, see the documentation).
At this stage in Pallas development, these are the types of contributions that are most appropriate:
That said, we encourage you to dive even deeper and submit PRs beyond these suggestions.
Pallas is still considered to be a prototype implementation, though some core features are close to done. It currently requires an underlying OS and file system, but has been designed such that these dependencies can eventually be removed.
Planned, but incomplete features:
This project is a fork of Plunder.