Live data from Hacker News

Elixir GenServer Explained

papercups.io

81–90 of 91 posts

Re: Elixir GenServer Explained

#81
post #79

Earlier quoted context omitted.

Oh that's so funny, because I'm an elixir user and we all claim that genserver (internal) organization is terrible. But yes, it is way better than callback hell... It just could be better still.

not genserver, what will you use?

[deleted]

Re: Elixir GenServer Explained

#82
post #79

Earlier quoted context omitted.

Oh that's so funny, because I'm an elixir user and we all claim that genserver (internal) organization is terrible. But yes, it is way better than callback hell... It just could be better still.

not genserver, what will you use?

There isn't anything. Dave thomas proposed a better way of organizing genservers, but I can't get behind it because his implementation is too magical and clever, but his idea for what the api should look like in the big picture is reasonable... I'm also generally in favor having everyone speak the same idioms, so it would take a very good implementation to get me off of genserver.

That said you shouldn't write genservers if you can help it. Using the frameworks genservers Is the best choice, Task is a better choice for most cases.

Re: Elixir GenServer Explained

#84

Thank you so much for sharing this. I've not yet finished a decade in the industry, nor ever laid an eye on Elixir before, and this made sense pretty quickly. Great job explaining it all! On a side note, its pretty amazing to see the impact on readability and understanding that native language features have on something like async. I've worked with similar systems to the example in the past in JS/Go/etc, and while yo…

I completely relate to the "code that just flows" statement. Cognitive overhead when dealing with code is a thing. I've been trying to explain this to a bunch of junior devs at every opportunity i get but all I get is a blank stare. Seems that the more senior and especially bittervet developers are a lot more sensitive to this. Thanks for sharing!

Re: Elixir GenServer Explained

#85
post #80

Earlier quoted context omitted.

It flows better because it is writing synchronous code. The code inside of every process in Erlang is synchronous. You achieve concurrency by spinning up processes (spawning functions). This isn't that different to firing an async function in Javascript, or starting a goroutine, etc. And you communicate between the processes by sending async messages. Those are the primitives. Asynchronously send messages between pro…

The formal framework for this is called CSP ("Communicating sequential processes"). I discovered this 15 years ago, and once you understand it, you will never have problems in multithreading again. https://arild.github.io/csp-presentation/#3

Not exactly. CSP is more what Golang adheres to (synchronous communication). The Actor Model predates CSP. And understanding it you'll still have problems...they're just different problems. You either won't be able to use a language with message passing as a concurrency primitive (i.e., almost all popular languages other than Go at this point), or you will, and now you'll have the time to spend on new types of problems (i.e., you can still create race conditions and deadlocks, and, in Erlang at least, now you get to start asking yourself what happens when things go wrong to a level you never would have with any other language).

Re: Elixir GenServer Explained

#86
post #5

Since these insert events are being buffered, what happens if the process dies? Are all of those inserts lost? I feel like Erlang/Elixir is designed to handle cases like this robustly, but it's not clear to me how this code avoids losing data when a process crashes, potentially up to 5s worth of updates!

Actually in Erlang/elixir, your own crash is something you can plan for and handle. if you check the code you will see in init() that the author captures exits. then there is a function terminate () which is called when a process exits for some reason (including crashes)

So for example one possible implementation of state is that you catch your own crash, save some state and then restore it when you restart

You aren't supposed to ever crash, however the whole language it's based around designing a framework to handle what happens if you do crash

So you design a whole framework of supervisors, watching processes, that all have a defined startup and shutdown order. You build structures with rules such as: if my cache module fails, them restart this whole chunk of application over here.

it's very cool

Re: Elixir GenServer Explained

#87
post #80

Earlier quoted context omitted.

The formal framework for this is called CSP ("Communicating sequential processes"). I discovered this 15 years ago, and once you understand it, you will never have problems in multithreading again. https://arild.github.io/csp-presentation/#3

Not exactly. CSP is more what Golang adheres to (synchronous communication). The Actor Model predates CSP. And understanding it you'll still have problems...they're just different problems. You either won't be able to use a language with message passing as a concurrency primitive (i.e., almost all popular languages other than Go at this point), or you will, and now you'll have the time to spend on new types of proble…

The nice thing about the cleanly independent processes is that by and large, "let that part crash, and keep going" is a perfectly adequate answer to most process states outside the pretty path, which is a real saving grace when working with wildly concurrent systems.

Re: Elixir GenServer Explained

#88

It is very interesting to me that components in OTP like GenServer, Register, and Supervisor are surprisingly intuitive if one tries to learn and play Erlang first, but not so obvious and possibly looks artificial if people don't learn Erlang in the first place. At first, I was surprised that the Erlang processes resemble the Actor model in such a minimalistic interface. Then I play with them for a while, I realized…

In BEAM circles, a modification go Greenspun's 10th rule is pretty popular:

"Any sufficiently complicated concurrent program in another language contains an ad hoc informally-specified bug-ridden slow implementation of half of Erlang."

Erlang's been around long enough at this point that for pretty much any problem in Erlang's area of expertice you find yourself solving twice, there's a damn good existing solution somewhere in the OTP.

Re: Elixir GenServer Explained

#89

Maybe a dumb complaint, but I hate the name. I have a really hard time not reading it as the active name "Generate Server". Like, I'd expect it to be a function that generates servers (whatever that means, since what it's dealing with isn't something I'd normally call "a server" either). It bugs the hell out of me every time I read Elixir code. One of those things I have to keep reminding myself: "OK, it's not what i…

It's a terrible name, anything starting with the abbreviated prefix "Gen" is hopelessly ambiguous. Is it "Generate"? "General"? "Generic"? "Genetic"? "Genesis"? While some would certainly be less frequently encountered, all can easily be found in programming context. When seeing "GenXxxxx" I could well understand someone feeling immediate brain-fog. That said, this is one of very few criticisms I can level at Elixir,…

Yup.

alias GenServer, as: GenericServer

Done.

Re: Elixir GenServer Explained

#90

Earlier quoted context omitted.

Not exactly. CSP is more what Golang adheres to (synchronous communication). The Actor Model predates CSP. And understanding it you'll still have problems...they're just different problems. You either won't be able to use a language with message passing as a concurrency primitive (i.e., almost all popular languages other than Go at this point), or you will, and now you'll have the time to spend on new types of proble…

The nice thing about the cleanly independent processes is that by and large, "let that part crash, and keep going" is a perfectly adequate answer to most process states outside the pretty path, which is a real saving grace when working with wildly concurrent systems.

Oh, certainly.

On this particular distinction, in fact, it's one of the benefits the actor model has over CSP. In the actor model (well, Erlang's take on it, but that's one that has been adopted elsewhere such as in Akka), I care about "if this fails, who should be affected by it". I.e., by default, it's just this process, and with a supervisor it'll restart. How do I get it restarting into a good state? If it goes down, who else needs to go down (to keep state consistent)? That's mostly all I have to ask myself; I don't have to ask what all the ways it could go down are (slight caveat there; you still want circuit breakers around external resources).

With CSP, I have to ask "if this goes down, who else IS affected by it". Not who should be, and be intentional about linking them, but determine who accidentally is affected by it. This is everyone who might be sending or receiving across a channel (to borrow Go parlance) this process has access to. Which breaks the abstraction; from the perspective of the current process I shouldn't have to know who is sending or receiving across a channel, but the reality is I -do-. And that's no better than the error model I have in other languages; I have to understand every part of my application to know what may or may not be affected by this one part going down. Go gets 'around' this by just making it so any uncaught exception (err, unrecovered panic) crashes the whole application, which is certainly one way to do it.

Post reply on HN