Core Erlang by Example
blog.erlang.org
blog.erlang.org
https://8thlight.com/blog/kofi-gumbs/2017/05/02/core-erlang....
Core Erlang is an intermediary language targeting 'BEAM', the Erlang virtual machine.
I've spent about 2.5 years of my career writing Erlang code. It's statements like "Core Erlang is less complicated than Erlang" that always trouble me about the Erlang community - Core Erlang is less complicated for compilers, not for humans. In this first in a series of blog posts about Core Erlang, there is no context given around what it is or why anyone should care.
Erlang is a great language and environment, but it really struggles with the marketing aspects of a platform, that is, how to make it accessible to humans.
The only complexity is figuring out OTP, which in itself is very easy to use, but you must understand all the underlying abstraction it's masking before really being good at it.
Once you get familiar with the boilerplate of distributed systems and see how OTP helps eliminate much of it, with a best-practice design figured out for you, that's when you really start to respect Erlang.
Of all the languages I've learned on the side I think Erlang/OTP has the most amount of features I feel are missing from other languages (proper pattern matching being the biggest).
F# patterns come from ML, with active patterns added on top. Erlang patterns come from Prolog, with unification removed.
A quick example, in Erlang you can write:
1> (X = {1 = A, [2, 3]}) = {1, [2, 3]}.
{1,[2,3]}
As well as: 1> ({A = 1, [2, 3]} = X) = {1, [2, 3]}.
{1,[2,3]}
That is, the `=` is actually a pattern matching operator and the order of its two operands doesn't matter (well, not really, not everywhere - that's where the "removed unification" comes from - but close enough). That's because in Prolog the `=` operator denotes actual equivalence, not an assignment, and Erlang inherited that.In F# and OCaml this is done with a special operator, `as`, and it cares about the order of its operands very much. You can do this:
> let (1 as a, [2; 3]) as x = (1, [2; 3]);;
val x : int * int list = (1, [2; 3])
val a : int = 1
but > let (a as 1, ...etc
obviously won't work.All in all, both mechanisms work quite well and you can translate between them easily, but they are not 100% equivalent. Erlang has much simpler syntax (it's basically just `=`) while F# adds `as`, `&` and other constructs. In Erlang, once a name is matched with an expression, it is equivalent to that expression and stays that way forever. In OCaml, you can readily reassign matched names. That also makes Erlang pattern matching act like an assert and (identity) comparison in some situations, which are both different things in F#.
So, the use-cases are very similar, but the concepts underlying the mechanisms differ which in turn makes some details of the mechanisms differ too. Both are a joy to work with, to be sure, compared to the "applied ifology" or the dreaded `switch` statement, though :)
It basically uses pattern matching as a fundamental/primitive part of the language design, rather than some useful add-on or tertiary feature.
case X of
{expense, N} -> Total - N;
{invoice, N} -> Total - N;
{loss, N} -> Total - N;
{credit, N} -> Total + N
end
whereas in ocaml (and F#, modulo details) you could just do something like: match x with
| (`expense, n) | (`invoice, n) | (`loss, n) -> total - n
| (`credit, n) -> total + n;;
The examples are unidiomatic and artificial and you can sometimes ameliorate it with guards, but it's a frequent (minor) annoyance.In Erlang's case (pun intended!) I'd probably go for a function though:
total({expense, N}, Total) -> Total - N;
total({invoice, N}, Total) -> Total - N;
total({loss, N}, Total) -> Total - N;
total({credit, N}, Total) -> Total + N. case X of
{Cat, N} when Cat == expense; Cat == invoice; Cat == loss -> Total - N;
{credit, N} -> Total + N
end> you can sometimes ameliorate it with guards
which is exactly what you did, so I don't think you're disagreeing here :)
I've been a Scala dev for years and I gradually learned FP by moving from Java to Scala, but in the case of Elixir, I've only barely looked at some Erlang previously.
Would you say Elixir has done a better marketing job of being accessible to developers?
In terms of language features, Elixir gives you (almost first-class) modules, protocols, and macros. They are all implementable in Erlang, for example qlc is a DSL implemented with parse transforms (which are macros), but Elixir gives them to you nicely packaged and ready to use.
It can also become a problem. I love Lisps, and love proper macros, but every other macro I see written by Elixir guys makes me cringe. Macros are a powerful tool and it takes time and effort to learn not to abuse them. In Lisps, even the younger ones, like Clojure, people only write macros if there's really no other way to do something. In Elixir, people write them for everything, they break hygiene habitually, and the language encourages that (the `use` and `__using__` are the biggest offenders here, IMHO). It's a trade-off between easy-to-write (in Elixir) and easy-to-read-and-debug (in Erlang). Both approaches are valid, but for different use-cases.
Also, please don't use rhetoric of the form "We need to make software accessible to/usable by humans." If the software is not usable by humans, who exactly is using it? To what species do they belong? Say "new users"/"people unfamiliar with the language"/etc. instead.
This blog entry is part of an ongoing series about the passes in an Erlang compilation. The author probably assumed some familiarity with Erlang itself, and previous entries in the series, not intending this entry for an audience of general unspecified humans. I'm not sure it's the author's duty to turn every technical post they write into newbie-accessible marketing.
It does therefore have a syntax heavily influenced by Prolog. But that doesn't mean that Prolog was the only incluence. The Erlang case expression for pattern matching doesn't really correspond to anything in Prolog, it does look more similar to ML-family languages. The multi-branch if expression looks like a Lisp cond form and again not that much like Prolog.
Is Erlang an abstraction on top of Core Erlang? I ask because the later is far more verbose than the former. Is there a performance or extensible benefit to this more verbose and precise form that I am not aware of?
The only benefit is that it is easier for a machine to do the transformation from Core Erlang to BEAM bytecode than it would be if that machine would have to transform Erlang in one go, similar to how a C compiler will first make a tree like representation of the program in memory, will then convert that tree into some assembly (or a meta assembly language that is still platform independent) which will then be converted to binary. Each of those stages has a corresponding equivalent in the Erlang domain:
Erlang -> C
Abstract Format -> AST
Core Erlang -> Assembler
BEAM Bytecode -> binary
The equivalence is obviously not exact but good enough for this explanation.
- fez ( https://github.com/kjnilsson/fez ) is an F# to Erlang compiler (compiling to Core Erlang)
A good intro with some word about core erlang too
https://skillsmatter.com/skillscasts/11312-fez-fsharp-type-s...