AnnouncementsMatrixEventsFunnyVideosMusicAncapsTechnologyEconomicsPrivacyGIFSCringeAnarchyFilmPicsThemesIdeas4MatrixAskMatrixHelpTop Subs
1

You guys can ignore this. It just helps me learn to write what I've learned in a place that has some consequence. Not a spiral notebook that will be guaranteed read 0 more times.

Today I learned a little bit more about Erlang. I learned a little bit about how it's VM works at the bytecode level. I may revisit that.

But more importantly I now have better ideas of how to use Erlang processes. There are about 5 or so reasons to use them and it's worth noting them so I know to reach when I need to. Let's do it in order of obviousness.

One is as an equivalent to javascript's async await. Spawn a function as a process with instruction to get a data out of some other system like a database or API and then block yourself with a receive statement until a response comes back.

Why this is better and worse than async await. Better is that the child process can write in the form of synchronous code. In javascript that would block everything. So everything because an async function, and every function call requires an await modifier. Tacky.

Bad for Erlang's method for this use case is that instead of mapping the promise directly to a variable we now need the child process to return a unique signature we can pattern match against and anticipate other kinds of messages coming in. Concurrent calls to two non-similar async things would be harder to Promise.all on. Often that's an over optimization in javascript anyway and slightly messy. But it seems in Erlang that would be even messier. I almost don't want to think about it. So it would likely enforce more a call one and await one then the next. That's not ideal for minimizing latency.

The next most obvious reason to reach for processes is with listeners, particularly with TCP sessions. When a new TCP connection comes in spawn the handler function in a new process. Without doing that, if the handler function can't really complete until the client sends all the data then it will block, meaning we couldn't receive new connections.

The next but not 100% obvious reason to reach for one is to use as a generator, asynchronous or otherwise. When I made the mult service in Erlang I wanted a generator so bad. Because of Erlang's dependence on recursion to iterate over a structures these traversals can get complicated. And just when you've figured out how to do that you realize that to extract data from this traversal you need to return some crazy data structure that will need to be passed up the other way through the traversal, mergable at every fork, all while nothing is mutable, and legible and reasonable on the other side. Holy shit that's complicated as hell. With if mutables existed you could do sentinal.push(found_data). Of course accumulators could act as sentinels but that's practically another form of recursive traversal just to modify this accumulator that you have to check if your existing traversal is going the same way or not. Also fucking complicated.

Hence why I wanted a generator so bad. One would want a guarentee.. if I can traverse it, sometimes a hard enough problem on it's own, I should be able to extract data from it. Well pass the PID of a parent along and call Parent ! {self(),FoundData}.

On the parent run loop(Acc) -> receive {CID,Data}-> loop([Data|Acc]); {CID,done}->Acc end.

Something like that. Or better yet just use the data right way instead of accumulating it. Out=lastprocess(Data),gen_tcp:send(Socket,Out)

The last reason to reach for one is to use an actor model. I thought immutability and an actor model were opposed. They are. But Erlang has a way around the mutability. It's recursion. Same as the one used to cheat immutability for the sake as loops. Think cheating immutability do recursion.

loop(State) ->
receive
{set_health, NewHealth} ->
loop(State#{health => NewHealth});
{get_health, From} ->
From ! {health, maps:get(health, State)},
loop(State)
end.

I really think that's such a cheat. I get that it's only mutable on a message and that's nice for some esoteric reasons but I don't see any stability advantage over explicitly re-assigning the value in an imperative language. It is still only getting updated once per message if our code is blocked until it gets a message. I think the same argument applies to loops. Having an assigned index variable in a for loop really seems equally safe. Not that indexes are used with lists typically in Erlang but if they were you could still recurse an out of bounds value all the same. Maybe it's really an argument for iterators over indexes. You could do that in C or Javascript. Never going out of bounds is a result of the data structure not using recursion, or immutability, or immutability that isn't immutability via recursion.

Any turing complete language is going to let you mutate things. Any system that can mutate can break. The trick isn't making mutation impossible. It's doing it in ways that things don't break. Maybe there is value in a language that makes you access mutation vs stores for the equivalent of S-expressions (lisp) through two different systems so you really are acknowledging and thinking about when you are mutating something verse passing values around or translating something. And I guess making the code get structured different to do it instead of adding or removing a const keyword makes it more enforced.

A few more reasons I won't write about.

Anyways that is my rambling notes. Just recalling back what I learned in the last hour, and forming opinions that may be wrong and likely will need to be updated as I learn. But you need to have an opinion to be able to adapt your opinions, maybe.

Summary: (async await, non-blocking listener, generator, actor model, supervision tree, concurence) -> use a process.

Comment preview