There's so little said about how it does what it does. The Github README says:
> The Acton Run Time System (RTS) offers a distributed mode of operation allowing multiple computers to participate in running one logical Acton system. Actors can migrate between compute nodes for load sharing purposes and similar. The RTS offers exactly once delivery guarantees. Through checkpointing of actor states to a distributed database, the failure of individual compute nodes can be recovered by restoring actor state. Your system can run forever!
> NOTE: Acton is in an experimental phase and although much of the syntax has been worked out, there may be changes.
I'd like a lot more info on how this checkpointing mechanism and discovery/communication works.
I tried using gleam for real work the other day and found myself really enjoying it. It's a cool language and surprisingly productive coming at it with no experience with OTP or the BEAM.
I know it's a me issue, but I got stumped by how inefficient it is to write code that handles linked lists in some cases. As far as I can tell, there's no built-in array or anything more convenient. I had to write a function to find items at indices, haha.
I looked through the docs and it seems like I'm not missing anything. I'm clearly too accustomed to web development
It's not just you, this is an issue with the BEAM platform itself. Singly-linked lists are the privileged sequential datastructure on the BEAM. I work in Elixir every single day and this is one of my pet peeves about the platform.
There actually are arrays [1] and ETS tables [2] that allow for fast random access and updates but they don't have first-class syntax in Erlang, Elixir, or (it seems) Gleam. They're just libraries, so you can't pattern match on them. And when it comes to the array datatype, approximately no one uses it. I don't think I've ever seen code in the wild that uses it.
I don't know why they don't build a first-class random-access vector datastructure like Clojure [3] that has a superset of BEAM's list functionality. I think at this point it's mostly just historical inertia: list has been there so long it would be a massive pain to change it.
Tuples are the BEAM's native arrays, but they're expensive to update, immutability means you (usually) have to copy the whole thing to update one index. Lists are a lot more common in functional languages.
If you really need random access you can use a map with integer keys. If you want to write numerical stuff, Elixir has Nx - not sure if it's usable from Gleam though.
for a second I thought it was a link to the "Action!" language which was a cool little language for the Atari 8bit computers back in the day, it came as a plugin cartridge...
I thought the same. Possibly because I recently wrote a compiler/interpreter for the Action! language. Not terribly useful in its current state, but most of the language is supported.
I was confused by that too. Under the "Types" page of the guide[1], they say:
> Every value in Acton is of a certain data type, which tells Acton what kind of data is being specified so it knows how to work with the data. Acton is a statically typed language, which means the type of all values must be known when we compile the program. Unlike many traditional languges like Java or C++, where types must be explicitly stated everywhere, we can write most Acton programs without types. The Acton compiler features a powerful type inferencer which will infer the types used in the program.
> Acton implements the Hindley-Milner (HM) type system, which is common in languages with static types, like Haskell and Rust. Acton extends further from HM to support additional features.
The language also has inheritance (and thus presumably subtyping), protocols (interfaces, I think), and generics. Historically, those features have not played nice with HM inference, so I'm not sure what's going on there.
Static: the types could be inferred. I haven't looked too closely yet.
Strong: Python is strongly typed with a similar syntax.
Strong vs weak typing means "how much information does the type system provide to me." Static vs weak typing means "when does the type system do its work."
I see no reason why a language with syntax like this could not be strongly typed. The static part is hard to claim until the type inference rules are explained.
why? type inference is generally quite nice. Makes for clean code. One of the things I've enjoyed from using things like F#. All the advantages of strong and static typing without the cruft.
It depends how it's applied. I like type inference when I'm defining a variable, for example. In Go, I can just write this:
message := "hello world"
...and the compiler knows it's a string, and I know it's a string because I can just hover it with my mouse and my IDE will tell me that. That's good enough for me.
But when we are talking about functions..:
# some fictional function I just came up with
def install_requirements(dependencies, opts):
run_setup(dependencies, opts)
return assert_requirements_installed(dependencies)
Now I'm confused. What is the data type of "dependencies" and "opts"? Is the IDE smart enough to tell me that? What if I'm building a function that assumes "dependencies" to be of type A, but the compiler thinks it can also be type "B"?
I don't know enough about compilers and type inference to know whether this is actually a problem in Acton or not. I wish they explained more. My gripe against this kind of inference is that it's impossible for my IDE to tell me what is being passed around. In Java, for example, I can CTRL+Click on a data type and the IDE will show me the definition; can Acton do the same?
message is a `str` since we know the literal "hello world" is a str. Some literals can be multiple types, like 123 can be `int` or `u64` or some other fixed int type.
You can specify the type explicitly if you want to
# some fictional function I just came up with
def install_requirements(dependencies: set[str], opts: dict[str, str]):
run_setup(dependencies, opts)
return assert_requirements_installed(dependencies)
which makes it easier to read the code which is a little bit more involved. It also helps guide the type inferrence & checking. Hindley-Milner type checking is notorious for doing a good job at type unification and a lousy job at error messages. Acton is no exception and is arguably worse than many other users of HM implementations since it is relatively young and all effort have gone into actual type checking and not much into errors.
Broadly speaking, I think the modern solution to IDE insight into types is to implement an LSP that provides types and other information to the editor. Acton does not have an LSP but it will.
I looked thru the docs assuming this might run on ERTS/BEAM but it doesn't seem so? Perhaps it is novel?
Personally I really struggle with Erlang syntactically. Elixir is a lot better... but I think Python is the king of syntax.
My biggest problem in life is actually not syntax related - it is "how to architect systems on actors". Writing modules/classes, the implentation, that stuff is really trivial for any decent programmer. The hard part to me is figuring out, how do I map my problem to these actors? How many supervisors do I have? Who supervises who? Transitioning from traditional RPC or process/thread based thinking to actor thinking has been hard for me. I yearn to understand it fully, but I have a block. You could perhaps say the same thing for languages like go and clojure who rely on channels for async communication. In some sense, you can consider a goroutine or a core/async function to be an actor, and the channel is an inbox (broad generalization here). The issues you face adjusting your thinking are similar.
This tool does not solve the aforementioned problem for me, that exists regardless. But I have long thought that Python with a distributed actor model and immutable datastructures that could circumvent the GIL would be the unstoppable language for building modern apps.
It is not ERTS / BEAM. Acton has its own run time system. Acton code is compiled to C functions, so the execution of functions look quite different between BEAM and Acton RTS. Acton has a Pythonic syntax but is NOT Python, so there is no GIL - quite the contrary, the Acton RTS runs actors concurrently.
I'm afraid that Acton in itself won't teach "how to architect systems on actors". I do wholeheartedly relate to the challenge. It's not easy to switch and think "natively" in a new paradigms. Go try it out, do Advent of Code in Acton and see how it feels ;)
Acton has a different focus than Erlang. Beyond syntax, the type system is likely the biggest difference, both in how in feels to the developer as well as what it means for things under the hood. I think Acton, given static typing, can compile down to more efficient code than what you can ever achieve with Erlang. From this perspective, Acton is more in the same camp as Rust or Go. But where Go is a language built around CSP, Acton, like Erlang, is built around the actor model. So if you like actors but also like types, maybe Acton is for you :)
There's lots of work to get types into Erlang, but it's hard to bolt things on afterwards, which is why Acton is a language and not just an actor framework for X.
It's not currently pluggable but there are quite few interaction points between RTS & DB so I imagine it could be done relatively easily. Depending on what you want to do, this might work well or become ultra-slow ;) Acton persists the execution of individual continuations (like actor methods but chopped up at every I/O point), so if you write functions in certain ways, you end up with many commits and if the database is not super fast, this can become a real chokepoint.
There are design for how to improve this in Acton through batching and asynchronous commits.
Sounds a lot like a language running on the BEAM (e.g. Elixir, Erlang, Gleam, etc): Distributed Computing built in; Durable State (e.g. ETS); Non-stop (i.e hot code upgrades); Actor model.
I think a lot of these things sort of come-for-free when you are an actor based language. Like actors are basically little processes communicating with each other, so you've mostly already abstracted away from memory pointers on your local machine, thus, it's trivial to move an actor to a remote computer but continue communicating with it in largely the same fashion. Clearly you need to be wary of latency but otherwise it looks about the same. Actors are single-threaded and have their own private state, so upgrading an actor is "just" replacing old code with new code but the same private data. You don't have to synchronize this access with other threads or similar. Actors just make this easier, which is why you'll probably find such features in languages using actors.
My first thought was that this language was named in reference to Lord Acton and his most famous quote "Power tends to corrupt and absolute power corrupts absolutely."
Much like C abstracts over machine code or Python over C, Acton attempts to provide abstractions that let you treat multiple computers connected together in a distributed system, as a single logical machine, much like programming your own local computer. This is what the "made easy" alludes to.
Acton leans heavily on actors and actors are already like small little processes that do their thing. With a distributed run time system, we can spread actors across multiple computers, yet let them communicate to each other, much as if they were on the same computer.
Yes, it does affect the local control flow. If you don't assign the return value, then the local actor will proceed execution of the next thing, so effectively async execution of the remote method call. You can also write this explicitly like so:
async remote_actor.foobar()
If you on the other hand want to wait for that remote actor to finish but still not assign locally you can use a dummy variable:
_ = remote_actor.foobar()
or explicitly ask to await
await async remote_actor.foobar()
You can also explicitly ask for async while grabbing the return value, which makes it easy to run things concurrently
a1 = async remote_actor1.foobar()
a2 = async remote_actor2.foobar()
print("a1 and a2 run concurrently")
r1 = await a1 # collect both results
r2 = await a2
print(r1, r2)
Yes indeed, assigning really turns into an await on an async value. You can explicitly write it as
a = await async remote_actor.foobar()
So the local actor goes to sleep, awaiting the remote, then the RTS notices the returned value and place the actor on the readyQ for execution, thus waking up from the await.
> The Acton Run Time System (RTS) offers a distributed mode of operation allowing multiple computers to participate in running one logical Acton system. Actors can migrate between compute nodes for load sharing purposes and similar. The RTS offers exactly once delivery guarantees. Through checkpointing of actor states to a distributed database, the failure of individual compute nodes can be recovered by restoring actor state. Your system can run forever!
> NOTE: Acton is in an experimental phase and although much of the syntax has been worked out, there may be changes.
I'd like a lot more info on how this checkpointing mechanism and discovery/communication works.