i think the original quote is "Any sufficiently complicated concurrent program in another language contains an ad hoc informally-specified bug-ridden slow implementation of half of Erlang" :-) but your point still stands
A single node erlang application would be one that doesn't use dist at all. Although, if it includes anything gen_event or similar, and it happens to be dist connected, unless it specifically checks, it will happily reply to remote Erlang processes.
my personal reality is that the majority of projects I've consulted on have seldom actually leveraged distributed erlang for anything. the concurrency part yes, clustering for the sake of availability or spreading load yes, but actually doing anything more complex than that has been the exception! ymmv tho!
I recommend taking a look at the various open source Riak applications too! Might not be updated to any sort of recent versions of erlang but was a great resource to me early on.
ahoy!! thanks for spotting the issue I wrote the post in a stream of consciousness after a long day! I'll make that edit and call it out!
the statement about what historically constitutes a large erlang cluster was an anecdote told to by Francesco Cesarini during lunch a few years ago — I'm not actually sure of the time frame (or my memory!)
likewise I'll update the post to reflect that! thanks ( ◜ ‿ ◝ ) ♡
> its actor implementation is not built upon Erlang/OTP
This seems to be the opposite of pragmatic.
The most pragmatic approach to actors when you're building a BEAM language would be to write bindings for OTP and be done with it. This sounds kind of like building a JVM language with no intention of providing interop with the JVM ecosystem—yeah, the VM is good, but the ecosystem is what we're actually there for.
If you're building a BEAM language, why would you attempt to reimplement OTP?
Because of type safety. The OTP lib is already great, but there are still some things missing, most requested being named processes. But there is work being done to figure out how to best make it work for gleam.
The question of type safety has come up so often here that I guess it's worth replying:
That's exactly what I mean by this not seeming pragmatic. Pragmatic would be making do with partial type safety in order to be fully compatible with OTP. That's the much-maligned TypeScript approach, and it worked for TypeScript because it was pragmatic.
Now, maybe Gleam feels the need to take this approach because Elixir is already planning on filling the pragmatic gradually-typed BEAM language niche. That's fine if so!
Type safety is one of the goals of the language I don't see a reason to throw it out of the window now. I see what you mean, but the type system is one of the things that makes gleam pragmatic. If you really need some missing OTP feature you can super easily step into Erlang using FFI and get it. That's one of the reasons the article doesn't call gleam pure.
And what has this approach gotten them? A language as complex as c++ and haskell combined, but that still has runtime type errors. A typescript backlash is coming.
It uses the same primitives as Erlang, the difference is that it exposes type safe APIs instead of untyped ones which you would get from using the Erlang abstractions.
It implements the same protocols and does not have any interop shortcomings.
I agree with the part about reusing OTP but some of the server syntax of Erlang and Elixir is not good IMHO. I never liked using those handle_* functions. Give them proper names and you cover nearly all the normal usage, which is mutating the internal state of a process (an object in other families of languages.) That would be the pragmatic choice, to lure Java, C++ programmers.
Elixir gives you Agent, which is what you want, but for reasons, Agent is a bad choice.
What you're not seeing with the handle_* functions is all the extra stuff in there that deals with, for example, "what if the thing you want to access is unavailable?". That's not really something that for example go is able to handle so easily.
defmodule Robot do
def handle_call(:get_state, _from, state) do
something()
end
def get_state() do
GenServer.call(__MODULE__, :get_state)
end
end
robot = Robot.start_link()
robot.get_state()
just let me write (note the new flavor of def)
defstatefulmodule Robot do
def get_state() do
something()
end
end
robot = Robot.new()
robot.get_state()
Possibly add a defsync / defasync flavor of function definition to declare when the caller has to wait for the result of the function.
The idea is that I don't have to do the job of the compiler. It should add the boilerplate during the compilation to BEAM bytecode.
I know that there are a number of other possible cases that the handle_* functions can accommodate and this code does not, but this object-oriented-style state management is the purpose of almost all the occurrences of GenServers in the code bases I saw. Unfortunately it's littered by handle_* boilerplate that hides the purpose of the code and as all code, adds bugs by itself.
So: add handle_* to BEAM languages for maximum control but also add a dumbed down version that's all we need almost anytime.
Ok, I kind of see what you're saying, but IMHO, you're trying to hide the central, enabling abstraction of BEAM environments, which is sending messages to other processes.
If you really don't like the get_state above, I think it'd make more sense to just ditch it, and use GenServer.call(robot, :get_state) in places where you'd call robot.get_state(). Those three lines of definition don't seem to be doing you much good, and calling GenServer directly isn't too hard; I probably wouldn't write the underlying make_ref / monitor / send / receive / demonitor myself in the general case, but it can be useful sometimes.
In my experience with distributed Erlang, we'd have the server in one file, and the client in another; the exports for the client were the public api, and the handle_calls where the implementation. We'd often have a smidge of logic in the client, to pick the right pg to send messages to or whatever, so it useful to have that instead of just a gen_server:call in the calling code.
In the early days of Elixir what you are proposing here was popular[1], but over time the community largely decided it wasn't beneficial and I rarely see it any more.
It is production ready and has been used for numerous non-trivial projects. Experimental in this context means there is expected to be API changes and feature additions in future.
reply