> 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 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
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.
[1] https://www.erlang.org/doc/apps/stdlib/array.html [2] https://www.erlang.org/docs/23/man/ets [3] https://clojure.org/reference/data_structures#Vectors
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.
https://en.wikipedia.org/wiki/Action%21_%28programming_langu...?
def multiply(a, b):
print("Multiplying", a, "with", b)
return a*b
How is this strong and static? If I don't need to specify what my function takes, isn't that duck typing? Maybe I'm missing something?It would be really nice to have strong, static types. It's the only thing keeping me from learning Elixir, so this could be a nice alternative.
> 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.
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.
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 = "hello world"
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.
Java doesn't track the types as well as HM does.
Specifically, if I treat a Stream as a Mappable, I lose the static type info, and can only get it back via casting.
In an HM language, e.g. Haskell, my object is statically a Stream type everywhere it's used, even if I'm doing Mappable stuff to it.
https://hexdocs.pm/elixir/main/gradual-set-theoretic-types.h...
Doing some searches based on that, I enjoy/endorse the "natural progression" analogy in https://github.com/actonlang/acton/discussions/1400#discussi...
Pony uses object capabilities in its stdlib, but its real value proposition is reference capabilities that provide memory safety at compile time.
I.e. solves the two generals problem?
Does someone wanna offer up a different definition of either term, or do we file this alongside the CAP-beaters?
Edit: I was too quick to judge!
In addition to the above, you don't "give up consistency", you get "the speed of C" (with GC), and "exactly once message delivery guarantees".
IMO, the pure operator precedence syntax of Erlang is peak homoiconigraphy. I prefer that over syntactic sugar. But also I'm a fan of Prolog...
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.
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 ;)
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.
click. click. click. click. click. click. click. click. click. click.
Ah a hello world! That answers everything
Is the persistence layer and the calls to persist/checkpoint pluggable/extensible?
There are design for how to improve this in Acton through batching and asynchronous commits.
Acton has its own run time system though which shares much more with Pony than with BEAM. Pony folks wrote a paper on distributed computing with actors - https://www.doc.ic.ac.uk/~scb12/publications/s.blessing.pdf
I’m intrigued. I never imagined anything about distributed systems would ever be easy. Those words just don’t go together in my mind.
Curious how the type system works.
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.
Which means the behavior changes when there is no return value?
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)
I guess this means when a return value is used, the call is synchronous, and after running the method processing resumes where things left off.
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.