Live data from Hacker News

Comparing Elixir and Go

blog.codeship.com

171–180 of 202 posts

Re: Comparing Elixir and Go

#171

If you want to really understand the philosophy that makes Erlang ( and Elixir ) beautiful ( and why it made me a better programmer ), this conference by Greg Young is a kind of eye opener : https://vimeo.com/108441214 . You realize then that clustering, hot reload, availability etc... are not only features but the logical consequence of a beautifully crafted environnement that aims at developer productivity. I'm som…

That was a great video, thanks! :)

Re: Comparing Elixir and Go

#172

Earlier quoted context omitted.

Pretty significant detail though, as no one has done either.. also "anyone" is fairly strong, building decent compilers and interpreters is pretty difficult

There is an AOT native compiler that ships as part of Erlang called HiPE. The original project has been incorporated into Erlang 15 years ago: https://www.it.uu.se/research/group/hipe/ The current setup is also mentioned on Wikipedia's page about AOT: https://en.wikipedia.org/wiki/Ahead-of-time_compilation

I was wondering if I should have mentioned HiPE in my comment.. but I couldn't remember if it was fully native. It was a few years ago that I was learning Erlang/Elixir.

Re: Comparing Elixir and Go

#173

If you want to really understand the philosophy that makes Erlang ( and Elixir ) beautiful ( and why it made me a better programmer ), this conference by Greg Young is a kind of eye opener : https://vimeo.com/108441214 . You realize then that clustering, hot reload, availability etc... are not only features but the logical consequence of a beautifully crafted environnement that aims at developer productivity. I'm som…

The way I find it simplest to "enlighten" people about Erlang's peculiar philosophy is its approach to scheduling:

The Erlang VM is "reduction"-scheduled. This means that the given Erlang process currently running on a scheduler thread can get pre-empted, but only as a result of executing a call/return instruction. (Effectively, the pre-emption is a check inside the implementation of the call/return opcode.) As long as you don't execute any call/returns (don't call functions and don't return from your own function), your function body can run as long as it likes.

This is a design choice: because processes won't be pre-empted "in the middle" of a function, any Erlang process can feel safe executing an instruction that calls into native code, while not having to worry that that native code could itself be pre-empted and leave dirty state in the Erlang process's heap while some other process gets scheduled and tries to then message or introspect that process. It gives you a lot of leeway "for free."

So how does Erlang ensure that processes don't hog a core forever, given that you could theoretically just write a loop that spins forever? Well, in Erlang, you can't write a loop. Instead of loops, you have tail-calls with explicit accumulators, ala Lisp. Not because they make Erlang a better language to write in. Not at all. Instead, because they allow for the operational/architectural decision of reduction-scheduling. Without loops in the language, every function body will execute for only a finite amount of time before hitting one of those call/return instructions, and thus activating the reduction-checker.

The Erlang "platform" has been shaped around the choices of how to best construct a production runtime that gives you "hard things" (like calling into native-code libraries while maintaining thread-safety) for free. Or rather, you could say that where everyone else pays these costs when they hit the particular problem, Erlang pays the cost up-front in the design of the language+platform and how you're forced to code at all times, in order to make these hard things easy.

The same is true of so many other Erlang things:

- how synchronous messaging has to be implemented on top of asynchronous messaging with expected reply-refs and timeouts, so as to make the sender process, rather than the receiver process, be the thing that defaults to crashing if the receiver doesn't recognize the message;

- how OTP-framework code has to be structured as delegate functions that return to the framework, so that the framework can "be there" in each process to handle hot code upgrades and process hibernation;

- how sockets either block (when {active, once}), or will saturate a process with packet messages (if just active) until that process crashes on overload--because the network listener is a separate part of the runtime that lives in a hot loop and wants to just be given a place to stuff packets into, and isn't allowed to do anything that's not an O(1) operation, like expanding the size of a process's message inbox;

etc.

Erlang is not a programming language in the sense that other languages are. Erlang was not designed from the language in. Erlang (ERTS) is a runtime, and was designed from the runtime out, with Erlang being effectively a pure side-effect: the language that ended up being required to interact with the features of the ERTS runtime.

Of course, you can also go back and apply some design sense to the language, and then you get something like Elixir. But, despite large visual differences "in the small", your large Elixir app will end up looking very much like a large Erlang app. And this is because a large part of what you're doing in an ERTS language is not programming using the language, but rather weaving together the features of the runtime. (Contrast: using DirectX vs. OpenGL to manipulate the GPU. Two very different APIs, but one "runtime" they're both speaking to, consisting of features like shaders et al.)

Re: Comparing Elixir and Go

#174
post #99

Earlier quoted context omitted.

It is possible to start external processes from BEAM and interact with them. I've blogged a bit about it at http://theerlangelist.com/article/outside_elixir You can also write NIFs (native implemented functions) which run in BEAM process (see http://andrealeopardi.com/posts/using-c-from-elixir-with-nif... ). The latter option should be the last resort though, because it can violate safety guarantees of BEAM, in parti…

I spent 30 minutes looking at NIF, but I was scared away. My understanding is that if the NIF crashes then BEAM crashes. Which leads me to think that if you need NIF then you need safety guarantees on the Native side that C can't provide.

Think of NIFs as Erlang's equivalent to Rust's unsafe{} blocks. It's where you write the implementations of library functions that make system calls, and the like. But, like unsafe{} blocks, you do as little as possible within them.

For example, if you want to call some C API from Erlang where the C API takes a struct and returns a struct, you'll want to actually populate the request struct--and parse the return struct--on the Erlang side, using binary pattern matching. The C code should just take the buffer from enif_get_binary, cast it into the req struct, make the call, cast the result back to a buffer and pass it to enif_make_binary(), and then return that binary. No C "logic" that could be potentially screwed up. Just glue to let Erlang talk to a function it couldn't otherwise talk to. Erlang is the one doing the talking.

On the other hand, if you have a big, fat library of C code, and you want to expose it all to Erlang? Yeah, that's not what NIFs are for. (Port drivers can do that, but you're about the right amount of terrified of them here: they're for special occasions, like OpenSSL.)

The "right" approach with some random untrusted third-party lib, is to 1. write a small C driver program for that library, and then 2. use Erlang to talk to it over some IPC mechanism (most easily, its stdio, which Erlang supports a particular protocol for.)

If you need more speed, you can still keep the process external: in the C process, create a SHM handle, and pass it to Erlang over your IPC mechanism. Write a NIF whose job is just to read from/write to that handle. Now do your blits using that NIF API. If the lib crashes, the SHM handle goes away, so handle that in a check in the NIF. Other than that, you're "safe."

Re: Comparing Elixir and Go

#175
post #160

Earlier quoted context omitted.

A simple GenServer ( a OTP behaviour ) linked to an ETS ( erlang in memory data store ) table would do the trick. Basically, It receives by message the inserts, and once the counter reaches x or timer reaches y secs, it inserts in the db. Thinking about it, 20 lines of code is already a bit verbose for it :)

In Elixir, you can use GenStage. (In fact, I'm writing a GenStage consumer right now.)

True. But haven't played with it yet. It seems nice and straightforward also.

Re: Comparing Elixir and Go

#176

In Elixir, error handling is considered “code smell.” I’ll take a second to let you read that again. I think that this makes a lot of sense. My experience in just about any language, is that the official means of error handling already feels like a code smell, even before you start using it. And if that's not the case, then it still manages to feel that way when used in a large project. Lots of Smalltalk projects wou…

Funny you mentioned smalltalk. Alan Kay was talking about how Erlang is really an OOP language more so than other languages. In term of message passing and each process is an object...

Erlang just let it crash and you can restart it with a supervisor process. I think most of the time this model is much better since you won't be doing numerical stuff with BEAM anyway, it's not for it and it's too slow.

Re: Comparing Elixir and Go

#177
post #130

Earlier quoted context omitted.

> So now compilers should only be written, if v1.0 is production ready?! No, but they are only relevant for the purposes of the discussion, that is, when considering whether to adopt a language platform based on if it's AOT or interpreted etc, when they are v1.0. That somebody can always make an interpreter for an AOT language, for example, is nothing people care about when checking whether to use a language for thei…

Turning on cynic mode, I would say the only thing that matters is which company backs a programming language and only languages offered on their OS SDKs are relevant. Anything else will just add entropy and development costs to projects, due to 2nd class tooling and lack of libraries on the target platform. Then it doesn't matter how the code gets compiled at all.

>Turning on cynic mode, I would say the only thing that matters is which company backs a programming language and only languages offered on their OS SDKs are relevant.

That's true, but for application programming. For the server side there are other considerations. WhatsApp can get away with using Erlang for example, and still get the sweet billions.

Re: Comparing Elixir and Go

#178

Earlier quoted context omitted.

I think it's a fare comparison. Much more than Rust vs Go which we see all the time. Technically, Rust and Go are more similar to each other than Go and Elixir but Rust and Go are intended for different areas. Go and Elixir on the other hand will compete directly as both have been built to write exactly same kind of software.

Rust and Go has not been written for same purpose and they target different audiences. Rust is targeting more towards safe systems programming while Go is mostly being used for network and service stuff. Both can do more obviously but let's not confuse the core usecase.

Yes, that's what I said. When you are starting a project where you need tight control and deal very close to metal, you'd choose between Rust, C++, C, D etc. Go wouldn't even be a choice here.

Instead if you project is a higher level application server, you'd choose between Go, Elixir, Erlang, Java, Python, Node etc. Go should be compared with these languages and ecosystems.

Re: Comparing Elixir and Go

#179

Earlier quoted context omitted.

Immutability probably does simplify a lot of things; but it isn't actually required to get Erlang's model, since Erlang isolates the state of processes from each other. Each process could have its own isolated mutable state as opposed to an immutable one and it wouldn't change the "let it crash" philosophy.

You can't guarantee that isolation without immutabilty, or at least forced copying. In Go, it's extremely easy to accidentally send a pointer (or a data structure that contains a deeply nested pointer somewhere; this includes embedded maps, slices and channels, which are all reference types) on a channel, which is bound to break mutability guarantees at some point.

[deleted]

Re: Comparing Elixir and Go

#180

Earlier quoted context omitted.

Immutability probably does simplify a lot of things; but it isn't actually required to get Erlang's model, since Erlang isolates the state of processes from each other. Each process could have its own isolated mutable state as opposed to an immutable one and it wouldn't change the "let it crash" philosophy.

You can't guarantee that isolation without immutabilty, or at least forced copying. In Go, it's extremely easy to accidentally send a pointer (or a data structure that contains a deeply nested pointer somewhere; this includes embedded maps, slices and channels, which are all reference types) on a channel, which is bound to break mutability guarantees at some point.

Mutable locals do not necessarily imply pointers...
Post reply on HN