The Acton Programming Language
74 points
20 days ago
| 17 comments
| acton-lang.org
| HN
karmakaze
20 days ago
[-]
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.

reply
leke
20 days ago
[-]
If this is your cup of tea, you should also take a look at Gleam.
reply
steve_adams_86
20 days ago
[-]
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

https://hexdocs.pm/gleam_stdlib/gleam/list.html

reply
asa400
16 days ago
[-]
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.

[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

reply
ramchip
16 days ago
[-]
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.

reply
leke
20 days ago
[-]
You should also try to reach out on the Discord channel. They're a very friendly community.
reply
steve_adams_86
19 days ago
[-]
That’s a great idea, thanks for the suggestion!
reply
dang
20 days ago
[-]
We changed the URL from https://github.com/actonlang/acton to the project page. Interested readers will want to look at both, of course.
reply
keithnz
20 days ago
[-]
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...

https://en.wikipedia.org/wiki/Action%21_%28programming_langu...?

reply
mypalmike
20 days ago
[-]
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.

https://github.com/mypalmike/RetrAction

reply
leke
20 days ago
[-]
There just needs to be a script version, and people will really get confused.
reply
tail_exchange
20 days ago
[-]
I'm a bit confused. The documentation says it is static and strongly typed, but the example for functions is:

    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.

reply
munificent
20 days ago
[-]
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.

[1]: https://acton.guide/types/intro.html

reply
cbarrick
20 days ago
[-]
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.

reply
tail_exchange
20 days ago
[-]
I think the word I'm looking for is explicit. It's not required to have explicit types in the functions, which I find disappointing.
reply
keithnz
20 days ago
[-]
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.
reply
tail_exchange
18 days ago
[-]
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?

reply
kll
16 days ago
[-]
It's about the same in Acton

    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.

reply
tail_exchange
13 days ago
[-]
Cool. Thanks for explaining!
reply
mrkeen
13 days ago
[-]
> In Java, for example, I can CTRL+Click on a data type and the IDE will show me the definition

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.

reply
monooso
20 days ago
[-]
In case you're not aware, Elixir is in the process of implementing gradual set-theoretic types.

https://hexdocs.pm/elixir/main/gradual-set-theoretic-types.h...

reply
deciduously
20 days ago
[-]
It could be both if all types are inferred.
reply
leke
20 days ago
[-]
I think Kotlin is an example of both.
reply
mlinksva
20 days ago
[-]
First ultra naive impression: Pony with orthogonal persistence.

Doing some searches based on that, I enjoy/endorse the "natural progression" analogy in https://github.com/actonlang/acton/discussions/1400#discussi...

reply
kulibali
20 days ago
[-]
It's unclear from a brief skim of the documentation what the story is around memory safety in Acton.

Pony uses object capabilities in its stdlib, but its real value proposition is reference capabilities that provide memory safety at compile time.

reply
mrkeen
16 days ago
[-]
"Transactional" & "distributed".

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".

reply
whalesalad
20 days ago
[-]
I have been longing for this exact thing for so long! Erlang style actors with python syntax. Legitimately excited for this.
reply
cbarrick
20 days ago
[-]
I was curious about the opposite: What does this offer on top of Erlang? Is it just a new syntax for effectively the same language?

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...

reply
whalesalad
20 days ago
[-]
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.

reply
kll
16 days ago
[-]
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 ;)

reply
kll
16 days ago
[-]
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.

reply
tobyhinloopen
16 days ago
[-]
Me clicking pages until I see examples:

click. click. click. click. click. click. click. click. click. click.

Ah a hello world! That answers everything

reply
kodablah
20 days ago
[-]
Interesting, this is very similar to some of the things we do to language runtimes at Temporal to ensure durability/resumability.

Is the persistence layer and the calls to persist/checkpoint pluggable/extensible?

reply
kll
16 days ago
[-]
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.

reply
abrookewood
20 days ago
[-]
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.
reply
708145_
20 days ago
[-]
I read the main feature to be durable storage or in other words persistent actors. Erlang Term Storage (ETS) I thought was in-memory.
reply
sausagefeet
20 days ago
[-]
Maybe they meant DETS
reply
kll
16 days ago
[-]
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.

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

reply
JackFr
16 days ago
[-]
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."
reply
VyseofArcadia
16 days ago
[-]
I assumed given Wayland and Weston exist that Acton was named after yet another small town in metrowest Massachusetts.
reply
agentultra
20 days ago
[-]
“Distributed systems, the easy way.”

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.

reply
herrington_d
20 days ago
[-]
Curiously the claim sounds like learning this programming language is equivalent to reading the whole DDIA book. :P
reply
kll
16 days ago
[-]
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.

reply
tobyhinloopen
16 days ago
[-]
Elixir is pretty easy
reply
cbarrick
20 days ago
[-]
The "next" button on the page titled "Variable data types" does nothing. It just links back to the same page.
reply
artemonster
20 days ago
[-]
THERE MUST BE CODE EXAMPLES ON YOUR FRONT PAGE! ugh. when will they learn.
reply
sigmonsays
20 days ago
[-]
This is weird to me: "A method is called asynchronously when the return value is not used."

Which means the behavior changes when there is no return value?

reply
kll
16 days ago
[-]
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)
reply
pepa65
20 days ago
[-]
Return value is not needed, so just "call away" -- asynchronously.

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.

reply
kll
16 days ago
[-]
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.
reply
leke
19 days ago
[-]
Like with python, are tabs syntax?
reply