
Erlang beauty - _nato_
http://blog.ikura.co/posts/erlang-beauty.html
======
dmix
I started off as a designer before learning to program and I have a minor
unhealthy attachment to the design and composition of functions. I've been
learning Erlang recently and I have to say Erlang's pattern matching and list
comprehension make it one of my favourite languages in terms of design (I
should note I'm also a fan of Scheme/Clojure style which I know many people
aren't).

It certainly didn't appear that way when I first came across Erlang, as it's
not always readable to the uninitiated (and nearly turned me off the language)
but brilliant for those that are familiar with it.

Elixir has also done a good job of translating it for people who like Ruby-
style syntax, I prefer the flexibility of the more verbose Erlang.

~~~
unoti
> Elixir has also done a good job of translating it for people who like Ruby-
> style syntax, I prefer the flexibility of the more verbose Erlang.

The really big deal about Elixir is the ability to do LISP-style runtime code
execution and generation. Lots of the Elixir language is implemented as
macros, and the beauty and power of it is evident when studying the source of
Elixir itself and Phoenix. A good example of where this comes into play is
implementing a gen_server: in Erlang, this is always a good bit of
boilerplate. In Elixir it can be done with a couple of lines, with automatic
default implementations provided.

The Elixir syntax is a nice bonus, but the runtime code-generation Elixir
brings to the table is a game-changer that takes it to the next level.

~~~
dave_ops
You can now do this in a sanctioned way in Erlang as of v18. They're including
merl as part of the standard distribution now.

~~~
unoti
This is a very big deal. Thank you for telling me about it! As is so often the
case, there's more value in the HN comments than the articles.

------
dragonwriter
> Someone smart, somewhere, at some time, did a study which concluded that the
> human brain can easily hold six or seven items in short-term memory with
> little trouble, but beyond that, it becomes taxing. This applies nicely to
> lines of code in a function. Amazingly, after all the above guidelines have
> been followed, bringing a function’s lines-of-code count down to a maximum
> of seven is surprisingly easy to do.

[https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus...](https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two)

This has got to place as one of the most misapplied research findings in
history. If you have an aesthetic preference for 7 as a limit to the number of
lines of code in a function, then, sure, do that, but don't pretend there is
science behind it (or at least not this _particular_ science.)

~~~
jacquesm
She may be wrong about that particular number, but it is really no secret that
reducing the scope is a key ingredient to solving problems and avoiding bugs.
Another key ingredient is to stay far away from side-effects and Erlang does a
fairly good job of riding that fine line between ideal theory and pragmatism.

edit: gender of author corrected, thanks Nathancahill

~~~
nathancahill
*she

When in doubt, 'they' is a safe bet too.

~~~
StavrosK
To be fair, in our circles, "he" will be right about 95% of the time.

~~~
nathancahill
:/

------
biokoda
Lets play devils advocate. Does pretty == readable?

I consider this:

foo(X) ->

    
    
        case X of 
    
            bar   -> void;
    
            _Else -> undefined
    
        end.
    

More readable than:

foo(X) -> foo1(X).

foo1(bar) -> void;

foo1(_Else) -> undefined.

First function is parsable in its entirety immediately. It conveys an idea in
a single fragment, which is not too large and it is not too small. Foo
executes depending on value of X.

Second function requires parsing two fragments of code. It requires a not-so-
pretty naming scheme of appending 1 to the function. And you can not grasp the
entirety of the matter in a single glance. To parse this function you must
follow a train of thought: foo -> foo1 -> execute depending on X.

Second function style of code results in a million functions in a single
module which I very much disagree results in more readable code.

Moving on to next example:

foo(X) -> final_function(maybe_function(X)).

foo(X) -> Maybe = maybe_function(X), final_function(Maybe).

I fail to see how second foo is more clear. In Erlang maybe value can be
absolutely anything. It is unnecessary verbose. The explanation on what is
wrong with first function contains way to many "shoulds" and not enough
"becauses".

~~~
rdtsc
> Second function requires parsing two fragments of code.

foo1 is used to illustrate a point not that you'd name functions like that.
The whole thing would be probably:

    
    
       foo(bar) -> void;
       foo(_) -> undefined.
    

I don't know, I see that better than the case statement.

~~~
biokoda
Appending 1 to functions is quite common in Erlang for cases such as that. You
cant find a good naming scheme for small fragments of ideas.

~~~
rdtsc
I don't know, I find I do it with variables more than functions. With
functions I seprate them by arity, so there could be a foo/0 and foo/1 maybe.
But usually I haven't seen much fun1 fun2 fun3 ... pattern.

And if you don't see a way, don't split it. I mean, make your code look good
and easy to understand for yourself and others, that's the point.

------
dave_ops
Seems reasonable to me... except I don't see the point of having foo/1 and
foo1/1\. The author could just have easily done:

    
    
      foo(bar) -> void;
      foo(X) -> undefined.
    

without the need for the proxy function:

    
    
      foo(X) -> foo1(X).

~~~
strmpnk
I think it was for demonstration. The example certainly is a bit contrived but
the general idea is that pattern matching in function heads is preferred over
pattern matching in case expressions by most experienced Erlang developers I
know and certainly by the author.

It's not a hard rule but there are definitely times when giving a name to a
set of patters is better than the anonymous case. Some other languages use
things like let-in and with to achieve similar ideas but Erlang is a rather
simple language so it just reuses function heads instead of nesting
definitions.

~~~
im_down_w_otp
Yeah, it makes for some very interesting uses cases for code-generation.

I've converted entire databases of mostly static data into nothing but a bunch
of exact-match function head signatures and let one of the fastest paths in
the VM be my "query planner".

It's insanely fast (at the expense of compile time) and the generated code is
really easy to read, trace, and debug.

~~~
fenollp
Intersting. Do you have an example somewhere?

------
technion
The beauty of Erlang became apparent to me when looking at a the structure of
Google's Certificate Transparency structures.

There are C implementations with loops, gotos and memcpy()'s. In Erlang I
could do this:

    
    
        <<Version:8, LeafType:8, Timestamp:64, LogType:16,
            ASNLen:24, Cert/binary>> = PemBin.

------
mml
Nice guidelines. Having finally put a few hundred lines of erlang under my
belt, it's clear to my that it's a wildly under appreciated language.

------
brianberns
The pipe operator is extremely elegant and helpful in F# and (I presume)
Elixir. I don't understand why the author considers it "malpractice".

~~~
strmpnk
Since Erlang doesn't have a pipe operator it's hard to compare but I'd imagine
using nested calls where a pipe might work is also considered inelegant in F#?

~~~
EdwardDiego
It's the "incredibly" that is tripping people up on that statement, I suspect.
I personally love the pipe operator in F#, and use the Clojure threading
macros in much the same way - it removes nesting, and it makes logic flow more
obvious

------
nathancahill
Excellent heuristics for any functional language, not just Erlang.

~~~
efnx
Not being able to pass functions as values is a show stopper - it's arguably
the single most important tenet of functional languages.

~~~
querulous
i believe she was talking specifically about `foo(bar(baz(X)))` and not
passing functions themselves as arguments

~~~
efnx
Ahh - that makes more sense. Still, this practice wouldn't make much sense for
the ML family of languages.

------
FLGMwt
How does everyone feel about the last item, the "seven lines per function
rule"?

I can definitely appreciate the readability of this assuming the functions and
intermediate variables are well named, but am I the only one that fears
negative PR feedback about adding cruft?

~~~
simoncion
> How does everyone feel about the last item, the "seven lines per function
> rule"?

As with all constructs that incorporate magic numbers, I _really_ , _rather_
dislike it.

Playing "follow the function call trail" can be just as harmful to code
readability as overly-long functions.

I _do_ strive to write functions that are as short as is reasonable, and I
_do_ lift single-use code blocks out into their own functions when doing so
improves clarity. However, I'm neither an English 101 student, nor a newspaper
journalist: I don't stress about whether my work meets some arbitrary length
criteria. :)

~~~
kybernetikos
It's amazing how people seem to miss that sending their readers jumping around
all over the place just to get an idea of what is going on can be worse than
long functions where the code reads coherently and simply.

I think _Clean Code_ has encouraged people to write a lot of single line
functions that get called from a single place (often with weird parameter
lists) as a means of replacing commented code blocks. While it has its place,
this is not a good pattern in general.

Good function writing is similar to what gets said for objects in OO design.
The function should do one cohesive thing that it is sensible to give a name
to and consider at least somewhat in isolation from its context. It should
take in a relatively small number of parameters (external dependencies for
classes), all of which should be the sorts of things you'd expect it to need
from its name.

~~~
simoncion
> I think Clean Code has encouraged people to write a lot of single line
> functions that get called from a single place (often with weird parameter
> lists) as a means of replacing commented code blocks.

 _Single line_ functions, called from a _single_ place? To replace commented
code blocks?? In the name of _improving_ readability!? What?! (!!!)

Unrelated to that: If your function name correctly and completely describes
the actions taken by that function and those that it calls, then replacing a
commented code block with that function isn't _unreasonable_.

 _shrug_ I guess the ages old advice: "Dogma is bad. Rules of thumb are often
useful." applies to the topics discussed in this subthread. :)

------
losvedir
Heh, FUCRS? At first skim, I thought the cheerleader chant spelled "FUNCS",
which is cute for Erlang. Is this just a jokey abbreviation of "fuckers", a
random set of letters given in a funny way, or am I missing something?

~~~
lostcolony
I don't think you are, no. It might have been a backronym, or it might be a
legit acronym, but either way it means nothing to me either (well, from a
programming, Erlang especially, context)

~~~
wetmore
Perhaps if they used 10 instead of 7 for the magic number, they could have
used CRUFT.

------
lukasb
Why

foo(X) -> foo1(X).

foo1(bar) -> void; foo1(_Else) -> undefined.

rather than just

foo(bar) -> void; foo (_Else) -> undefined.

?

~~~
mml
foo(_Else). -> undefined. Doesn't seem very erlangy. I'd expect a runtime
error, as someone's passed foo() the wrong value.

~~~
simoncion
> Doesn't seem very erlangy. I'd expect a runtime error, as someone's passed
> foo() the wrong value.

It depends.

Sometimes you want to crash when handed an unexpected value.

Sometimes you expect that you'll be handed unexpected values and need to
either fail more gracefully, print a warning and then fail, or warn and keep
going, or whatever.

Erlang gives you a bunch of tools, and they all have their place. [0] :)

[0] With the possible exception of the if statement... which is of
_questionable_ utility.

~~~
asabil
The "if" keyword should have been "when" instead. It's useful in some cases,
but rarely used.

------
kazinator

      foo(X) ->
          case X of 
              bar   -> void;
              _Else -> undefined
          end.
    

> _would become:_
    
    
      foo(X) -> foo1(X).
    
      foo1(bar)   -> void;
      foo1(_Else) -> undefined.
    

Incomprehensible rubbish, made more obfuscated by the second form. Okay, X
appears to be an identifier bound as a parameter. So then why doesn't bar
behave that way in

    
    
       foo1(bar)   -> void
    

is it because the X symbol is upper case? Or one letter wide? (Yuck, if so.)
Or does it have something to do with that semicolon? (Double yuck.)

At a glance, how the heck do we know when a symbol denotes a binding, and when
it denotes itself?

"_Else" is beautiful? It reminds one of the "_Bool" retch, vomited up in C99
by a drunken ISO C committee; but at least that is hidden by a #define bool
_Bool that everyone halfway sane uses in its place.

In any event, splitting case statements into pattern matching functions isn't
"lucid". Unless you're doing OOP, it's largely stupid. The case statement
keeps the relevant cases together. It "en-case-apsulates", pardon the pun. The
only reason that the individual functions retain clarity is that they are
grouped close together, so we, the readers, can reconstruct them as the case
handling aggregate that they are.

~~~
simoncion
> Incomprehensible rubbish, made more obfuscated by the second form. Okay, X
> appears to be an identifier bound as a parameter. So then why doesn't bar
> behave that way in...

It appears that you don't understand Erlang syntax. Go back and read these two
chapters [0][1] of Learn You Some Erlang, and revisit the article.

[0] [http://learnyousomeerlang.com/starting-out-for-
real](http://learnyousomeerlang.com/starting-out-for-real)

[1] [http://learnyousomeerlang.com/syntax-in-
functions](http://learnyousomeerlang.com/syntax-in-functions)

~~~
kazinator
From your reference [0]: _(note that variables begin with an uppercase
letter)_

Seriously? Making case not merely distinct but _semantically significant_ is a
deal breaker for me.

(Does that at least work for all Unicode characters that support case
distinction? If we start an identifier with a Δ, is that a variable name, as
opposed to starting with δ?)

> _It appears that you don 't understand Erlang syntax._

Yet I formed a correct hypothesis: the meaning does hinge on the case of X.

I only formed this hypothesis because I'm jaded.

The designer of a non-clusterfuck syntax would be offended by such a cynical
hypothesis. ("How can you even _think_ I made case semantically significant in
a symbol?")

I was hoping someone would tell me exactly that and set me straight about how
it _really_ works. Like maybe that X and bar are assumed to be declared
somehow or whatever.

~~~
simoncion
> Does that at least work for all Unicode characters that support case
> distinction...

Erlang was designed and first written back in the 1980's, so characters that
fall within the latin1 encoding are the only acceptable characters in atom and
variable names. However, UTF-8 source files are _fully_ supported, as is
Unicode in lists and binaries. There is a proposal to make Unicode characters
accepted for atom and variable names, but it hasn't been accepted yet, as the
details are (as you allude to) tricky. [0]

> The designer of a non-clusterfuck syntax would be offended by [the
> conclusion that I came to].

I can't agree with that. Initially, I thought that the case-significance was
strange, but -as I used the language- I quickly found that the ability to
distinguish at a glance between an atom and non-atom type is _very_ valuable.
These are things that you don't understand until you learn about the language,
and maybe actually _use_ it some.

Erlang's syntax is strange, but not even within ICBM distance to being a
clusterfuck. Frankly, Python's significant whitespace has caused me _far_ more
lost hours than any strangeness in Erlang's syntax.

> I only formed this hypothesis because I'm jaded.

Heh. I expect that the fact that you've likely seen _many_ imperative and some
functional languages in your day, and that Erlang is -despite its syntax
quirks- a language that is rather well designed and makes sense once you look
at it for a few minutes, has far more to do with it than your disaffectedness.
:) [1]

From your other comment:

> I promise you, I will not write another word anywhere about Erlang.

That's a pity. Erlang is _rather_ quite good at what it's designed to do, and
a _real_ pleasure to use. _Many_ folks report that Elixir is _at least_ as
rewarding to work with.

[0]
[http://www.erlang.org/eeps/eep-0040.html](http://www.erlang.org/eeps/eep-0040.html)

[1] As an aside, I note that in your _opening_ comment, you mention that the
syntax is "incomprehensible rubbish", but yet you correctly determined a
pertinent feature of the language syntax in very little time at all. This
kinda puts the lie to your claims of incomprehensibility. ;)

