POV-Ray : Newsgroups : povray.off-topic : The mysteries of Erlang Server Time
30 Jul 2024 06:24:08 EDT (-0400)
  The mysteries of Erlang (Message 1 to 10 of 29)  
Goto Latest 10 Messages Next 10 Messages >>>
From: Invisible
Subject: The mysteries of Erlang
Date: 9 Mar 2011 06:05:36
Message: <4d775f00$1@news.povray.org>
OK, so Erlang is something of an interesting case. There are several 
reasons for this:

- There are many functional programming languages. (Haskell, OCaml, 
Clean, and for some reason people keep calling Lisp "functional" too.) 
However, Erlang is *the only* one that could be considered "commercially 
successful", as far as I can tell.

- Other languages attempt to be *concurrent*, but only Erlang is 
*distributed*.

- The system is supposedly insanely reliable. People toss around "nine 
9s up-time" as if this is some sort of experimentally verified *fact*.

- The language claims to do all sorts of wacky, far-out stuff like 
trivial concurrency and distribution, hot-swapping running code, 
detecting and correcting run-time errors, and so forth. I want to see 
how it's done.

I had a look at Erlang.org, but it's difficult to find anything that 
really explains the interesting parts of the system. I can find plenty 
of "Hello World" programs, and "this is how you run stuff remotely", but 
nothing about "this is why we did it this way", or "this is how it 
works" or "this is how to use it to build non-trivial applications".

Suffice it to say, from what little I could discover, I didn't like what 
I was seeing. Like most commercially successful languages, Erlang is 
obtuse, complex, ugly and kludgy. Much like C, Java or anything else 
wildly popular. It's abundantly clear that Erlang is about as 
"functional" as Lisp (i.e., not at all). Still, people claim that 
Ericsson's entire business is based on it, and lots of people are using 
it, so it must have got *something* right.

Eventually I discovered a file entitled Armstrong_theses_2003.pdf So I 
spent the last two days reading this 300-page tome in the hope of 
enlightenment.

Predictably, the author spends most of that page count either repeating 
himself, or telling me in minute detail about things which are of 
utterly no interest to me. However, I did learn some interesting things.



In a way, Erlang is a bit like Haskell: Fundamentally, it does something 
extremely inefficient. And yet it seems to work just fine.
In Haskell's case, it's not allowing you to modify things in-place 
(except under very controlled conditions). In Erlang's case, it's 
refusing to allow processes to share state. This immediately implies 
that if you want to send data from place to place, you must copy it all. 
It's rather like Haskell's restriction, only much worse.

Then again, if one process is operating on a completely different node, 
you are *forced* to copy data from place to place anyway. It is 
unavoidable. So in a way, forcing you to do it all the time just makes 
it that little bit simpler to move from local to distributed coding.

One thing that rapidly becomes clear (and doesn't seem to be mentioned 
anywhere else) is that for Erlang, processes are about more than just 
concurrency or distribution. They are about fault isolation. A 
fundamental design assumption of the Erlang system is that stuff /will/ 
break, and /when/ it does, we want to maximally limit the damage. Part 
of the philosophy is that if one process dies, it shouldn't hurt anybody 
else.

Whereas in some other system you might structure things as separate 
processes for increased performance or because it's more logical to 
program it that way, in Erlang you might do this for no other reason 
than the fact that one task can sensibly continue if the other fails. 
You might use processes purely for error-handling reasons.

Another thing that becomes painfully obvious is that writing your 
program in Erlang does not somehow magically make it reliable. The 
language doesn't implement any kind of automatic error recovery system, 
as you might be mislead to believe. Instead, Erlang provides more or 
less the same kinds of error handling that any other language provides - 
catch/throw, etc. You can nest exception handlers, re-throw or modify 
exceptions, generate synthetic exceptions, and so on. And, as usual, any 
uncaught exceptions at the "top level" just make the process die.

The difference is what happens when a process dies. Rather than just 
dying, this situation is *detected*, and you can *react* to this. You 
can make it so that related processes get auto-killed as well 
(presumably because they can't sensibly complete without the dead 
process), or you can make it send a notification to some monitor 
process. The *language* itself allows notifications to be sent, but 
nothing more. The *libraries* allow you to do sophisticated error 
recovery, but *you* have to implement this. It doesn't happen by magic, 
as many seem to suggest.

Very importantly, the system correctly handles things other than 
software exceptions. Detecting division by zero is one thing. Detecting 
that the machine in Australia that you were talking to just got hit by a 
small pyroclastic flow is another. Obviously, no documentation actually 
describes how this works.



So how does Erlang actually work then? I mean, you can create processes. 
OK, then what? Well, as best as I can determine, each process has a 
"mailbox", and you can send asynchronous messages to any process that 
you have the address of. (And these messages can contain process 
addresses.) This works the same way both locally and remotely.

Sending is a non-blocking operation. Receiving is a blocking operation, 
and it makes use of Haskell-style pattern matching (which seems like a 
rather neat fit here). You can also time out waiting for a message - 
something which would be really hard to implement yourself, and which is 
probably extremely important in networking code.

Message sending has the curious property that message delivery is /not/ 
guaranteed, but message ordering /is/ guaranteed. Like, WTF? I'm not 
even sure how it's possible to tell that the messages are in order 
without being able to tell if you got all of them, but whatever. Seems 
very, very random to me. The thesis claims that implementing delivery 
checks yourself is easy, but implementing ordering checks is very hard. 
So they put the hard thing into the language, and left the easy thing up 
to you if you need it. Um, OK?

One of the very trippy things about Erlang is that I can be sitting on 
my PC in England, and I can just tell some Solaris server in Brazil to 
spawn a new process. As expected, no word on how the hell this actually 
works. I would have *thought* this means that the necessary code gets 
beamed over the wire, but some documentation seems to suggest that you 
have to install it yourself. And yet, you can apparently send arbitrary 
functions as messages, so...?

There's also no word on authentication. I mean, how does the machine on 
Brazil know that I'm authorised to be executing arbitrary code on it? My 
best guess is that if you're a telecoms giant, you have a private data 
network that nobody else can physically access, and you use that to 
control your equipment. In other words, you're authorised if you can 
actually access the system in the first place.



It's a similar story when we come to code hot-swapping. Surprise, 
surprise, it turns out that you can't just magically hot-swap code, just 
because it's written in Erlang. No, you have to do *actual work* to make 
this possible.

The language itself allows two (and only two) copies of the same code to 
be loaded at once, an "old" version and a "new" version. And it provides 
a way to switch from one to the other for a specific process. And that's 
*all* it gives you. Everything else has to be done, by hand, by you.

That means that you have to design your application so that there's some 
sort of signal that you can send it which will make it switch to the new 
version. So that takes care of your code, but what about your data? 
Well, presumably your new code might have new data layouts or other 
invariants, so you probably want to run some conversion function to port 
the running environment to your new code.

In short, _you_ have to figure out how your new code is different from 
your old code, _you_ have to write the code that converts any data 
structures or establishes any new invariants, _you_ have to check that 
this new code actually works properly, and _you_ have to design your 
application in the first place so that you can tell it to switch to the 
new code, running the conversion routine in the process.

And if you want to downgrade back to the old version? _You_ have to do 
all the work for that too. Really, the only help that Erlang is giving 
you is the ability to easily change from one chunk of code to another. 
You could probably do the self-same thing in Java, if you built your 
application so that every class has a version number, and there's some 
way to tell the application to load a set of new class files and execute 
a predetermined method on one of them. (I suspect Java's inflexibly type 
system would probably whine though...)

Erlang really gives you no assistance at all beyond the minimal level of 
"you can have two modules with the same name". And it's limited to just 
two, by the way. If you try to load a third version, anything running 
the first version is unceremoniously killed. And there's no mention of 
any way to detect whether old code is still running. (Perhaps there is, 
but I didn't see it mentioned.)

There was some talk of a packaging system, which sounds quite 
interesting, but obviously no details are described. It talks about 
being able to group modules into applications, and group applications 
into releases which can have complex dependencies, build procedures and 
installation processes. But... no details. So maybe it does 
configuration management for you, or maybe it does very little to assist 
you. I couldn't say.



The language itself has a few interesting features. Of course, being 
designed as something easily parsable by Prolog, the syntax is utterly 
horrid, it uses a kludgy preprocessor rather than actually supporting 
named constants, and so forth. It's also dynamically typed, which some 
people are presumably going to argue is somehow "necessary" because of 
what it does. I can't help thinking that a powerful type system would 
have made it /so much easier/ to make sure you got everything straight.

Just reading the document, I saw endless examples of data structures 
who's meaning is ambiguous due to the lack of types. For example, it is 
apparently impossible to tell the difference between a process that died 
because of an exception, and a process which merely sent you a message 
that happens to be a tuple containing the word "EXIT". The advice for 
this presumably being "don't do that".

It's slightly bizarre. Erlang looks for all the world like a crude 
scripting language with no safety at all and abysmal run-time 
performance. And yet, the language is designed for running network 
switches, possibly the most demanding high-performance hard-realtime 
system imaginable, and people claim it has nine 9s up-time. The only 
container types are linked lists and tuples, and yet the standard 
libraries somehow include cryptography and complex network protocols. 
Very odd...

So anyway, in spite of all the obvious bad things, there /are/ a few 
interesting bits. You get Haskell-style pattern matching, but with a 
twist. In Haskell, patterns are required to be "linear". (Don't you just 
love how that single word is used to mean a million different unrelated 
things?)

In Haskell, a variable is not allowed to appear twice in the same 
pattern. If you want to things to be equal, you must bind them to two 
different variables and then use a guard to check their equality. But 
Erlang has no such limitation. It /automatically/ desugars a non-linear 
pattern into a linear one with a guard. So there!

Erlang uses the rather baffling convention that mere variables start 
with an upper-case letter, while proper nouns such as function names or 
data points start with a lower-case letter. This makes it surprisingly 
hard to read Erlang code. (Haskell, not to mention English, obviously 
uses the opposite convention.)

I already mentioned that message receipt is by pattern matching. This 
seems like a rather powerful way to work, especially considering that a 
message that fails all patterns stays queued in the mailbox. So you can 
actually use patterns to decide what order you want to process incoming 
messages in. That seems quite sophisticated. And I already mentioned how 
you can add a time-out as well, which is obviously a very frequent 
requirement for hard-realtime systems.

In Haskell, general practise is to make all functions "total". In 
particular, all pattern matching should cover every case if possible. By 
contrast, apparently the general practise with Erlang is to /not/ cover 
cases which aren't expected to occur, and just let the thing throw an 
exception on a pattern-match failure. The rationale apparently being 
that the automatically-generated exception string is just as descriptive 
as anything that you could write by hand yourself, and it makes the code 
less cluttered.

Certainly some people have a tendency to make their functions try to 
return a valid result no matter how silly the inputs are. This is almost 
certainly not the way to write reliable software. Myself I've always 
thought that "***exception: Data.Map.lookup: key not found" is far more 
informative than "***exception: Data/Map.hs, lines 12312-12315: 
non-exhaustive patterns in case expression". In the first case, it's 
instantly obvious what the problem is, and that the bug is likely to be 
in the caller. In the second case... uh...?

Still, either way, if a key being missing is something that you're 
anticipating, you can and should handle the exception in the caller (and 
it won't matter what the text of the exception actually is). And if it's 
a high-availability system, if the exception /is/ uncaught, you have a 
program bug, and you probably want to log huge volumes of data about it, 
/and then/ restart the thing ASAP before somebody notices it went wrong!



I read the part about "behaviours" and face-palmed. The idea is that the 
standard library contains skeletons for things like "a server", and you 
supply the body code that makes it "a name server" or "a unique ID 
server" or whatever. Get this:

- The body code has to go in its own special module.

- The module has to export a specific set of functions with specific 
names and specific numbers and types of arguments returning a specific 
data structure as the result. (If only Erlang had type signatures, eh?)

- The skeleton passes an opaque "state" object between these functions, 
which the functions can update.

What does that sound like to you? Yes, congratulations, you've just 
invented object-oriented programming. But without dynamic binding or 
inheritance. (Or static typing, for that matter.) A "module which 
exports a special set of functions" is basically a *class*, and this 
"threading opaque state from function to function" is basically a 
stateful object.

Of course, modules don't have inheritance. That means that you can't use 
an abstract class to define what methods a class is supposed to have. It 
means you can't write a half-implemented class that other concrete 
classes can inherit from. But it also means that you don't have to worry 
about the nightmare of multiple inheritance either.

And there's another little twist: /Objects/ have state. But /functions/ 
passing around a mostly /immutable/ state gives the system a somewhat 
functional flavour. Indeed, in one of the example programs, a function 
receives some data, starts processing it, but if a certain condition 
happens, it /reverts to the old state/ rather than keeping the new one. 
This is jaw-droppingly hard to do if you use real objects with real 
mutable state. But in a semi-functional approach, it's quite trivial.

This whole "module with special functions" monstrosity is all the more 
weird because Erlang also has

1. first-class function names

2. lambda functions

Um, like, WTF? Why does the code have to go in its own module? Why can't 
I just pass you a tuple of function names, or even lambda functions, 
that define the callbacks? Hello??



One really neat feature of the language, which I didn't look into *too* 
deeply, is the binary operators. Given that IP datagrams and so forth 
are complicated assemblages of binary data fields with different 
alignments and so forth, packing and unpacking binary data is kind of 
crucial in networking code. Accordingly, Erlang not only allows you to 
write binary literals, but to /pattern match/ on them. Eat that!

I didn't look at it too closely, but it looks quite neat. You write an 
integer (or variable) followed by the number of bits. If you're packing, 
the requested data gets put into the appropriate places. If you're 
pattern matching, an integer obviously matches against that integer, 
while a variable causes that many bits to be copied into the variable. 
(I'm fuzzy on whether it becomes an integer or stays a binary...)

Not sure what happens if you need to write bytes backwards, as some 
broken protocols and file formats require. Is there a directive for 
that? Or do you have to do it by hand?



Many things about Erlang still remain unexplained. I still have 
absolutely no clue WTF an "atom" is. Nor have I discovered what this 
"OTP" thing that gets mentioned every 3 sentences is. I have utterly no 
idea how the "registered process names" thing works. Erlang is supposed 
to be for very high-performance systems, and yet to send any data from 
one process to another you have to /copy/ it. It seems like that can't 
possibly scale, but somehow it does. Apparently there's a distributed 
database system written in Erlang, but I didn't find any mention of 
that. And so on...

Still, I only have 1 PC, so what do I need distributed programming for? ;-)


Post a reply to this message

From: clipka
Subject: Re: The mysteries of Erlang
Date: 9 Mar 2011 07:54:57
Message: <4d7778a1$1@news.povray.org>
Am 09.03.2011 12:05, schrieb Invisible:
> OK, so Erlang is something of an interesting case. There are several
> reasons for this:
...
> - The system is supposedly insanely reliable. People toss around "nine
> 9s up-time" as if this is some sort of experimentally verified *fact*.

That uptime claim is probably not based on reliability, but on the 
ability to hot-swap code at runtime. Without such a feature, even the 
most reliable computer system (hardware + software) will have 
considerable downtime now and again just for the sake of updating the 
software.

As for reliability, with Wings 3D being written in Erlang I'd say 
apparently you can write unstable code in Erlang, too.


Post a reply to this message

From: Invisible
Subject: Re: The mysteries of Erlang
Date: 9 Mar 2011 08:10:43
Message: <4d777c53@news.povray.org>
>> - The system is supposedly insanely reliable. People toss around "nine
>> 9s up-time" as if this is some sort of experimentally verified *fact*.
>
> That uptime claim is probably not based on reliability, but on the
> ability to hot-swap code at runtime.

It's probably based on what the system was *designed* to do, rather than 
any *actual* measurement of its reliability.

> Without such a feature, even the
> most reliable computer system (hardware + software) will have
> considerable downtime now and again just for the sake of updating the
> software.

Well, that's true enough. But Erlang is hardly the only system to allow 
you to dynamically load code. Even C lets you do that, if you're 
masochistic enough...

> As for reliability, with Wings 3D being written in Erlang I'd say
> apparently you can write unstable code in Erlang, too.

I didn't find Wings unstable. Incomprehensible, sure. But unstable? No, 
not really.

Besides, writing unstable code is easy. It's writing *stable* code 
that's hard. ;-)


Post a reply to this message

From: clipka
Subject: Re: The mysteries of Erlang
Date: 9 Mar 2011 08:23:45
Message: <4d777f61$1@news.povray.org>
Am 09.03.2011 12:05, schrieb Invisible:
> Not sure what happens if you need to write bytes backwards, as some
> broken protocols and file formats require. Is there a directive for
> that? Or do you have to do it by hand?

What you call "broken" is just a different convention: There is no such 
thing as a "natural" byte order, unless you're really digging as deep as 
the baseband level -  where it again breaks down to a convention issue, 
because it depends on whether multi-bit values are sent MSBit first or 
LSBit first.

Fun fact: The famous MSByte-first "network byte order" used in the 
TCP/IP protocol family does /not/ match the bit ordering of the famous 
Ethernet, which is LSBit-first. So if there is such a thing as a broken 
protocol with regard to bit/byte ordering, TCP/IP-over-Ethernet is one 
of them.

(That said, I guess a language that is deeply rooted in the world of 
network protocols will have plenty of ways to deal with byte & bit 
ordering.)


Post a reply to this message

From: Darren New
Subject: Re: The mysteries of Erlang
Date: 9 Mar 2011 13:22:03
Message: <4d77c54b$1@news.povray.org>
Having worked with Erlang, here's some comments.

Invisible wrote:
> - There are many functional programming languages. (Haskell, OCaml, 
> Clean, and for some reason people keep calling Lisp "functional" too.) 
> However, Erlang is *the only* one that could be considered "commercially 
> successful", as far as I can tell.

Erlang isn't functional. It's single-assignment. The "functional" bit in 
Erlang doesn't give you benefits like it does in Haskell because there's 
still a whole bunch of non-functional operations. However, since you can 
only assign to each variable once, it means you can't have loops and you 
can't do a sequence of operations without making up new meaningless names 
for the data at each step.

If it was *actually* functional, you could put your own functions in guards, 
for example.

> - Other languages attempt to be *concurrent*, but only Erlang is 
> *distributed*.

Yep.

> - The system is supposedly insanely reliable. People toss around "nine 
> 9s up-time" as if this is some sort of experimentally verified *fact*.

It is. They've been running a commercial switching network with it for 
something like 10 years, and one of the six data centers lost power or 
something and all the machines in it for 20 minutes once, so they had a 15% 
downtime for 20 minutes out of ten years.

> - The language claims to do all sorts of wacky, far-out stuff like 
> trivial concurrency and distribution, hot-swapping running code, 
> detecting and correcting run-time errors, and so forth. I want to see 
> how it's done.

It's pretty straightforward. The rest is in libraries. The collection of 
libraries that make it easy is called OTP. The documentation on OTP is 
either non-existent or blows mooses, depending on what you can find.

> Suffice it to say, from what little I could discover, I didn't like what 
> I was seeing. Like most commercially successful languages, Erlang is 
> obtuse, complex, ugly and kludgy. Much like C, Java or anything else 
> wildly popular. It's abundantly clear that Erlang is about as 
> "functional" as Lisp (i.e., not at all). Still, people claim that 
> Ericsson's entire business is based on it, and lots of people are using 
> it, so it must have got *something* right.

Yes. It got the reliability right.

> refusing to allow processes to share state. This immediately implies 
> that if you want to send data from place to place, you must copy it all. 

No, not true. Since the data is immutable, you don't actually need to copy 
it if sender and receiver are on the same processor. It *acts* like it got 
copied, but then "X = [5, Z]" acts like it copies Z into X also.

> Then again, if one process is operating on a completely different node, 
> you are *forced* to copy data from place to place anyway. It is 
> unavoidable. So in a way, forcing you to do it all the time just makes 
> it that little bit simpler to move from local to distributed coding.

Except you're not really forced to copy stuff. And big things (like code or 
large binaries) get allocated in their own heap anyway.

> One thing that rapidly becomes clear (and doesn't seem to be mentioned 
> anywhere else) is that for Erlang, processes are about more than just 
> concurrency or distribution. They are about fault isolation.

Indeed, that was the fundamental goal. Not distributed computing, but 
reliable computing, however that may be achieved.

> Another thing that becomes painfully obvious is that writing your 
> program in Erlang does not somehow magically make it reliable. 

There are a handful of primitives that are reliable. You have to build 
everything else on top of that. For example, the spawn-link function gives 
you the basis for reliability, because there's no race condition between 
spawning the new process and linking to its error messages.

> less the same kinds of error handling that any other language provides - 
> catch/throw, etc. 

Huh. I didn't even know it provided catch/throw. I've never seen that used, 
actually.

> can make it so that related processes get auto-killed as well 
> (presumably because they can't sensibly complete without the dead 
> process), 

Yes.

 > or you can make it send a notification to some monitor
> process. The *language* itself allows notifications to be sent, but 
> nothing more. The *libraries* allow you to do sophisticated error 
> recovery, but *you* have to implement this. It doesn't happen by magic, 
> as many seem to suggest.

OTP is the libraries that make it seem like magic. However, being typical 
undocumented libraries that you learn by spending 5 years as an employee of 
Erricson, written in a language so modular you can't even figure out what 
pieces of code are part of the program and what aren't let alone read the 
code to see what it does, I've never quite figured it out beyond the very 
surface level.

> Very importantly, the system correctly handles things other than 
> software exceptions. Detecting division by zero is one thing. Detecting 
> that the machine in Australia that you were talking to just got hit by a 
> small pyroclastic flow is another. Obviously, no documentation actually 
> describes how this works.

There is documentation.

Each hardware box is running one interpreter, each of which is running one 
thread per CPU. Each interpreter keeps a TCP socket open to all the other 
interpreters it knows about, as well as sending periodic "are you there" 
messages. That TCP socket is also used to send application-level messages to 
remote processes. If the socket breaks, anything on the local system linked 
to that socket gets a notification that it broke.

There's also a separate process running on each local machine that's 
monitoring the local Erlang interpreter that will reboot the machine if the 
local Erlang interpreter stops working. (For some definition of "working" 
which isn't clear from the docs, but clearly includes answering keep-alive 
probes.)

> So how does Erlang actually work then? I mean, you can create processes. 
> OK, then what? Well, as best as I can determine, each process has a 
> "mailbox", and you can send asynchronous messages to any process that 
> you have the address of. (And these messages can contain process 
> addresses.) This works the same way both locally and remotely.

Yep.

> Sending is a non-blocking operation. Receiving is a blocking operation, 
> and it makes use of Haskell-style pattern matching (which seems like a 
> rather neat fit here). You can also time out waiting for a message - 
> something which would be really hard to implement yourself, and which is 
> probably extremely important in networking code.

Correct.

> Message sending has the curious property that message delivery is /not/ 
> guaranteed, but message ordering /is/ guaranteed. Like, WTF?

Message delivery isn't guaranteed because it's asynchronous, and the remote 
machine may crash between the time you send the message and the time the 
remote machine receives it. Ordering *is* guaranteed because if the remote 
machine crashes before it gets message #5, it won't get message #6 either.

 > I'm not
> even sure how it's possible to tell that the messages are in order 
> without being able to tell if you got all of them, but whatever. 

You can't, but the *sender* doesn't know if you got them all. The receiver does.

> very, very random to me. The thesis claims that implementing delivery 
> checks yourself is easy, but implementing ordering checks is very hard. 
> So they put the hard thing into the language, and left the easy thing up 
> to you if you need it. Um, OK?

Yep. Knowing if something is out of order is hard in a single-assignment 
language. By the time you do the pattern match, it's too late to not get the 
message out out of the queue. But the runtime can just look at the sequence 
number of messages from that other machine and pretend the message didn't 
arrive until the missing ones show up.

Given it all runs over TCP, tho, I don't think that's actually a whole lot 
of trouble.

Knowing the receiver got the message is as simple as adding code to send an 
ack each time you handle a message.

> One of the very trippy things about Erlang is that I can be sitting on 
> my PC in England, and I can just tell some Solaris server in Brazil to 
> spawn a new process. As expected, no word on how the hell this actually 
> works. I would have *thought* this means that the necessary code gets 
> beamed over the wire, but some documentation seems to suggest that you 
> have to install it yourself. And yet, you can apparently send arbitrary 
> functions as messages, so...?

The functions are merely references to installed code. The code itself has 
to already be in Brazil, and the function you send is "this module, this 
name, this version".

There's a document out there somewhere that lists the binary formatting of 
the contents of all the messages.

Starting the process remotely works because the local interpreter has (or 
establishes) a TCP link to the remote machine. All the messages for all the 
processes on your PC go over the same link to the machine in Brazil. There 
isn't one connection per communicating process.

> There's also no word on authentication. I mean, how does the machine on 
> Brazil know that I'm authorised to be executing arbitrary code on it? 

Nope, you're fucked.

> best guess is that if you're a telecoms giant, you have a private data 
> network that nobody else can physically access, and you use that to 
> control your equipment. In other words, you're authorised if you can 
> actually access the system in the first place.

Yes, exactly. Or you proxy the connection between machines through a VPN 
with authentication. Basically, you secure it at the machine-to-machine 
level, which is done outside of Erlang's source code and instead done at the 
Erlang interpreter level.

> It's a similar story when we come to code hot-swapping. Surprise, 
> surprise, it turns out that you can't just magically hot-swap code, just 
> because it's written in Erlang. No, you have to do *actual work* to make 
> this possible.

Well, sure.

> The language itself allows two (and only two) copies of the same code to 
> be loaded at once, an "old" version and a "new" version. And it provides 
> a way to switch from one to the other for a specific process. And that's 
> *all* it gives you. Everything else has to be done, by hand, by you.

The two-versions limit is because the code itself isn't GCed. They document 
that they could have GCed the code, but that would have added lots of 
overhead for something relatively rare.

If you *really* need more than two versions, first move to a "new" version 
that talks to one of N versions, then start up the N versions as appropriate.

> In short, _you_ have to figure out how your new code is different from 
> your old code, _you_ have to write the code that converts any data 
> structures or establishes any new invariants, _you_ have to check that 
> this new code actually works properly, and _you_ have to design your 
> application in the first place so that you can tell it to switch to the 
> new code, running the conversion routine in the process.

Yep.

> And if you want to downgrade back to the old version? _You_ have to do 
> all the work for that too. Really, the only help that Erlang is giving 
> you is the ability to easily change from one chunk of code to another. 
> You could probably do the self-same thing in Java, if you built your 
> application so that every class has a version number, and there's some 
> way to tell the application to load a set of new class files and execute 
> a predetermined method on one of them. (I suspect Java's inflexibly type 
> system would probably whine though...)

You'd have to be able to pass the network connections around too, remember.

> Erlang really gives you no assistance at all beyond the minimal level of 
> "you can have two modules with the same name". And it's limited to just 
> two, by the way. If you try to load a third version, anything running 
> the first version is unceremoniously killed. And there's no mention of 
> any way to detect whether old code is still running. (Perhaps there is, 
> but I didn't see it mentioned.)

There is, but it's deep in the bowels of "system" stuff.

The OTP system provides all the packaging and maintenance for the higher 
levels of management. Complaining that Erlang itself doesn't automate such 
stuff is like complaining that you need a Linux package manager because the 
file system only knows about files, not applications. :-)

> There was some talk of a packaging system, which sounds quite 
> interesting, but obviously no details are described. It talks about 
> being able to group modules into applications, and group applications 
> into releases which can have complex dependencies, build procedures and 
> installation processes. But... no details. So maybe it does 
> configuration management for you, or maybe it does very little to assist 
> you. I couldn't say.

It does a bunch of stuff, including giving you ways to roll things forward 
and back and such.

Realize that most of these "systems" you're talking about are actually 
implemented as Erlang code. So, for example, to compile code, you actually 
sit down at the REPL and invoke the compiler function. To package code, you 
sit down at the REPL and invoke the packager function. To debug code, you 
sit down at the REPL and invoke the debugging functions. Etc.

> The language itself has a few interesting features. Of course, being 
> designed as something easily parsable by Prolog, the syntax is utterly 
> horrid, it uses a kludgy preprocessor rather than actually supporting 
> named constants, and so forth. It's also dynamically typed, which some 
> people are presumably going to argue is somehow "necessary" because of 
> what it does. I can't help thinking that a powerful type system would 
> have made it /so much easier/ to make sure you got everything straight.

I agree about the syntax etc. It's quite possible a better type system would 
make it easier.

You should also check out Hermes, which is essentially the same thing with 
an astoundingly strict and high-level type system.  Indeed, Hermes is kind 
of a high-level Erlang, in that you (for example) let the compiler/runtime 
decide where to run the process and how many to create. You write a loop 
that says "read a message, process it, send an answer, loop" and it gets 
automatically parallelized. You write a process that says "read a message, 
process, send an answer to where this message came from, exit" and it gets 
turned into an in-line subroutine. Etc.

> Just reading the document, I saw endless examples of data structures 
> who's meaning is ambiguous due to the lack of types. For example, it is 
> apparently impossible to tell the difference between a process that died 
> because of an exception, and a process which merely sent you a message 
> that happens to be a tuple containing the word "EXIT". 

Well, it's possible unless you asked "turn all crashes from processes I'm 
watching into EXIT messages."

> The advice for 
> this presumably being "don't do that".

No, this is actually more like "We did that on purpose in case you want the 
supervisor to react as if you exited while you keep running." So, for 
example, you can get the supervisor to spawn a new handler process while the 
previous handler process rolls back the transaction that failed in *its* 
child process or something.

> It's slightly bizarre. Erlang looks for all the world like a crude 
> scripting language with no safety at all and abysmal run-time 
> performance. And yet, the language is designed for running network 
> switches, possibly the most demanding high-performance hard-realtime 
> system imaginable, and people claim it has nine 9s up-time. The only 
> container types are linked lists and tuples, and yet the standard 
> libraries somehow include cryptography and complex network protocols. 
> Very odd...

It's possible to link C (or anything else) into Erlang. The GUI for Erlang 
launches a separate Tcl/TK process and talks over a socket to it to do the 
drawing, for example.

In general, network switching isn't that hard real-time, because you usually 
have custom hardware to do the switching. A 5ESS (which can handle up to 
800,000 phone lines) runs on two 6800s, one of which is a hot spare. But of 
course there's a large lump of hardware that's routing the individual bytes 
here and there, so the 6800 only actually gets involved when you connect or 
disconnect, basically.

> Erlang uses the rather baffling convention that mere variables start 
> with an upper-case letter, while proper nouns such as function names or 
> data points start with a lower-case letter. This makes it surprisingly 
> hard to read Erlang code. (Haskell, not to mention English, obviously 
> uses the opposite convention.)

Erlang is the only language I've found whose syntax sucks worse than C++, 
yet it has way, way less built in than C++.

The primary problem, I think, is it started out as "let's investigate how we 
can write a million lines of code that doesn't crash."  Of course, once you 
have that million lines of code and it's running reliably, you're not going 
to go back and fix anything as trivial as the syntax.

> I already mentioned that message receipt is by pattern matching. This 
> seems like a rather powerful way to work, especially considering that a 
> message that fails all patterns stays queued in the mailbox. So you can 
> actually use patterns to decide what order you want to process incoming 
> messages in. That seems quite sophisticated. And I already mentioned how 
> you can add a time-out as well, which is obviously a very frequent 
> requirement for hard-realtime systems.

It's very handy, yes.

> In Haskell, general practise is to make all functions "total". In 
> particular, all pattern matching should cover every case if possible. By 
> contrast, apparently the general practise with Erlang is to /not/ cover 
> cases which aren't expected to occur, and just let the thing throw an 
> exception on a pattern-match failure. The rationale apparently being 
> that the automatically-generated exception string is just as descriptive 
> as anything that you could write by hand yourself, and it makes the code 
> less cluttered.

Well, that's not quite true for message pattern matching. For matching a 
pattern against a value, yes. But if you don't read messages, you leave them 
in the input buffer, which then grows and grows until you take out the 
entire interpreter. Normally you'd find the place in your code where you 
*think* you handled all the messages, and you'd put in a "if I match 
anything else, crash out" branch to the pattern match.

> - The body code has to go in its own special module.

A module is the granular unit of code update, so this makes sense.

> - The module has to export a specific set of functions with specific 
> names and specific numbers and types of arguments returning a specific 
> data structure as the result. (If only Erlang had type signatures, eh?)

There is that.

> - The skeleton passes an opaque "state" object between these functions, 
> which the functions can update.

Yep.

> What does that sound like to you? Yes, congratulations, you've just 
> invented object-oriented programming. But without dynamic binding or 
> inheritance. (Or static typing, for that matter.) A "module which 
> exports a special set of functions" is basically a *class*, and this 
> "threading opaque state from function to function" is basically a 
> stateful object.

Yep.  Except since it's actually a separate process, it's called an "actor", 
not an "object". :-)

> Of course, modules don't have inheritance. That means that you can't use 
> an abstract class to define what methods a class is supposed to have. It 
> means you can't write a half-implemented class that other concrete 
> classes can inherit from.

No. You'd instead write something that invokes functions in a module whose 
name you provided when you instantiated this module. There's actually even 
syntax for this. You don't "inherit" but you can have essentially "abstract 
modules."

> This whole "module with special functions" monstrosity is all the more 
> weird because Erlang also has
> 
> 1. first-class function names
> 
> 2. lambda functions
> 
> Um, like, WTF? Why does the code have to go in its own module? Why can't 
> I just pass you a tuple of function names, or even lambda functions, 
> that define the callbacks? Hello??

Because then you can't update the module. One of the things OTP is providing 
for you is the whole infrastructure for updating your module. That's why you 
send the "state" back to OTP and why the code has to be in a separate 
module. When OTP gets the "update" message, it invokes the new module with 
the state.

> One really neat feature of the language, which I didn't look into *too* 
> deeply, is the binary operators. 

They are pretty cool, yes. Handy, but I've seen it done better elsewhere. :-)

> Not sure what happens if you need to write bytes backwards, as some 
> broken protocols and file formats require. Is there a directive for 
> that? Or do you have to do it by hand?

I think there is. Lots of stuff can come after a second colon to control 
that sort of thing.

> Many things about Erlang still remain unexplained. I still have 
> absolutely no clue WTF an "atom" is. 

An atom is an interned string. Same as a "symbol" in LISP or Smalltalk. The 
trick is that in a message, an atom is an index into the atom table, so it's 
short regardless of how long the atom is.

> Nor have I discovered what this 
> "OTP" thing that gets mentioned every 3 sentences is. 

It's the libraries that take the basic operations Erlang supplies (like 
"load new code" or "spawn link") and turn it into things like installable 
packages.

> I have utterly no 
> idea how the "registered process names" thing works. 

A process can say "My mailbox is the registered process on server ABC for 
XYZ".  Another process can say "Give me XYZ@ABC" and get that mailbox. It 
works by shchlepping non-Erlang messages between the erlang processes, just 
like the keep-alives do.

 > Erlang is supposed
> to be for very high-performance systems, 

Not really.

> Apparently there's a distributed 
> database system written in Erlang, but I didn't find any mention of 

Mnesia. They were originally going to call it Amnesia, but someone pointed 
out that naming a database system after an illness characterized by 
forgetting things is probably a bad idea.

It's a cute little system, but it has its flaws due to being implemented on 
top of a single-assignment language and keeping everything in memory. For 
example, if the node crashes and you have a gigabyte of data in the table of 
that process, it has to go through reading all the logged updates and 
applying them to the tree in memory before it'll actually start answering 
queries. I guess if you can afford several machines running the same 
database entirely out of RAM, it's not much of a problem.

It's basically a distributed transactional system on top of the built-in 
database tables whose TLA name escapes me at the moment.

http://www.erlang.org/doc/man/mnesia.html

Note that all these things, OTP and Mnesia, along with stuff like the 
compiler, the debugger, the profiler, the package manager, etc etc etc, are 
all documented as "here's the functions you use to do it in Erlang." Almost 
none of these things are actually command-line tools.

-- 
Darren New, San Diego CA, USA (PST)
  "How did he die?"   "He got shot in the hand."
     "That was fatal?"
          "He was holding a live grenade at the time."


Post a reply to this message

From: Darren New
Subject: Re: The mysteries of Erlang
Date: 9 Mar 2011 13:22:52
Message: <4d77c57c$1@news.povray.org>
clipka wrote:
> That uptime claim is probably not based on reliability, but on the 
> ability to hot-swap code at runtime.

It's both.

> As for reliability, with Wings 3D being written in Erlang I'd say 
> apparently you can write unstable code in Erlang, too.

Yes. But Erlang gives you the tools to recover from the instability without 
taking down the whole system.

-- 
Darren New, San Diego CA, USA (PST)
  "How did he die?"   "He got shot in the hand."
     "That was fatal?"
          "He was holding a live grenade at the time."


Post a reply to this message

From: Darren New
Subject: Re: The mysteries of Erlang
Date: 9 Mar 2011 13:25:07
Message: <4d77c603@news.povray.org>
Invisible wrote:
> Well, that's true enough. But Erlang is hardly the only system to allow 
> you to dynamically load code. Even C lets you do that, if you're 
> masochistic enough...

No, technically, it isn't C doing the dyanmic loading. :-)

In any case, the benefit that Erlang gives you in terms of dynamically 
loading the code is (A) it's automatic, and (B) it lets the old code finish 
up while the new code is running. It's like letting currently-running web 
pages finish on the server while you're serving the new web pages to someone 
else. Yeah, we pretty much can solve that now, but it was a necessary 
component to the whole reliability enchalada.

-- 
Darren New, San Diego CA, USA (PST)
  "How did he die?"   "He got shot in the hand."
     "That was fatal?"
          "He was holding a live grenade at the time."


Post a reply to this message

From: Invisible
Subject: Re: The mysteries of Erlang
Date: 10 Mar 2011 05:37:20
Message: <4d78a9e0@news.povray.org>
On 09/03/2011 06:22 PM, Darren New wrote:
> Having worked with Erlang, here's some comments.

In what capacity are you "working with Erlang"? As I understand it, the 
main selling points of the language are reliability and distribution. 
While I doubt anyone actually *wants* unreliable software, there can't 
be too many problems that require distributed programming.

> Erlang isn't functional. It's single-assignment. The "functional" bit in
> Erlang doesn't give you benefits like it does in Haskell because there's
> still a whole bunch of non-functional operations. However, since you can
> only assign to each variable once, it means you can't have loops and you
> can't do a sequence of operations without making up new meaningless
> names for the data at each step.

Erlang is partially functional, in that functions are apparently 
first-class, and it's got single-assignment and immutable data 
structures. On the other hand, it doesn't enforce referential 
transparency; you can put side-effects anywhere you like. The document I 
read claims that it's considered beneficial to limit side-effects to 
just a few places in the codebase, and write most of the code in a 
"pure" style - which is exactly the same as for Haskell. It's just that 
the language itself doesn't /enforce/ any kind of strong separation 
between pure and impure code.

> If it was *actually* functional, you could put your own functions in
> guards, for example.

Wait - you *can't* do this already?? o_O

Why on earth would you design it this way?

>> - The system is supposedly insanely reliable. People toss around "nine
>> 9s up-time" as if this is some sort of experimentally verified *fact*.
>
> It is. They've been running a commercial switching network with it for
> something like 10 years, and one of the six data centers lost power or
> something and all the machines in it for 20 minutes once, so they had a
> 15% downtime for 20 minutes out of ten years.

One data point is scientific fact?

> It's pretty straightforward. The rest is in libraries.

Yeah, as I quickly found out.

> The collection of
> libraries that make it easy is called OTP. The documentation on OTP is
> either non-existent or blows mooses, depending on what you can find.

Just like Haskell, then. :-}

>> refusing to allow processes to share state. This immediately implies
>> that if you want to send data from place to place, you must copy it all.
>
> No, not true. Since the data is immutable, you don't actually need to
> copy it if sender and receiver are on the same processor.

OK, fair enough.

(Presumably both processes have to be running within the same logical 
"node", but then again also presumably you wouldn't have more than one 
node on the same box except for test purposes anyway...)

>> One thing that rapidly becomes clear (and doesn't seem to be mentioned
>> anywhere else) is that for Erlang, processes are about more than just
>> concurrency or distribution. They are about fault isolation.
>
> Indeed, that was the fundamental goal. Not distributed computing, but
> reliable computing, however that may be achieved.

As I say, this doesn't really seem to be spelled out anywhere else that 
I could find, and it seems a rather crucial detail.

>> less the same kinds of error handling that any other language provides
>> - catch/throw, etc.
>
> Huh. I didn't even know it provided catch/throw. I've never seen that
> used, actually.

Yeah they do. It's in there. Apparently the catch functions turn an 
exception into a tuple begining with 'EXIT' and followed by some stuff. 
Using throw you can make catch return an arbitrary data structure. 
(Quite how you tell the difference between an exception and a normal 
result is left to the imagination... It looks like you can use throw to 
make it look like the catch block exited normally.)

> OTP is the libraries that make it seem like magic. However, being
> typical undocumented libraries that you learn by spending 5 years as an
> employee of Erricson, written in a language so modular you can't even
> figure out what pieces of code are part of the program and what aren't
> let alone read the code to see what it does, I've never quite figured it
> out beyond the very surface level.

I'm glad it's not just me...

> There is documentation.
>
> Each hardware box is running one interpreter, each of which is running
> one thread per CPU. Each interpreter keeps a TCP socket open to all the
> other interpreters it knows about, as well as sending periodic "are you
> there" messages. That TCP socket is also used to send application-level
> messages to remote processes. If the socket breaks, anything on the
> local system linked to that socket gets a notification that it broke.
>
> There's also a separate process running on each local machine that's
> monitoring the local Erlang interpreter that will reboot the machine if
> the local Erlang interpreter stops working. (For some definition of
> "working" which isn't clear from the docs, but clearly includes
> answering keep-alive probes.)

Right. So it just sends heartbeat messages? (I presume the bit about 
rebooting the machine is optional; you wouldn't want your PC to reboot 
just because Wings 3D crashed, for example.)

>> Message sending has the curious property that message delivery is
>> /not/ guaranteed, but message ordering /is/ guaranteed. Like, WTF?
>
> Message delivery isn't guaranteed because it's asynchronous, and the
> remote machine may crash between the time you send the message and the
> time the remote machine receives it. Ordering *is* guaranteed because if
> the remote machine crashes before it gets message #5, it won't get
> message #6 either.

You mean that if message #5 doesn't arrive, message #6 is actually 
guaranteed not to arrive?

>> very, very random to me. The thesis claims that implementing delivery
>> checks yourself is easy, but implementing ordering checks is very
>> hard. So they put the hard thing into the language, and left the easy
>> thing up to you if you need it. Um, OK?
>
> Yep. Knowing if something is out of order is hard in a single-assignment
> language.

OK...

> Given it all runs over TCP, tho, I don't think that's actually a whole
> lot of trouble.

TCP guarantees both delivery and ordering. Unless the connection 
actually breaks. So is that all Erlang is saying? That it if one of the 
machines catches fire, Erlang can't guarantee that messages will be 
delivered to it?

These people are /serious/ about reliability. o_O

> Knowing the receiver got the message is as simple as adding code to send
> an ack each time you handle a message.

Network protocols 101: It's not that simple. It is possible for the 
message to arrive, but the acknowledgement itself to fail to be delivered.

Now, if you're running on TCP, so that ordering and delivery are 
guaranteed (unless there's no transport or no endpoint), the only way 
this can happen is if the transport or the endpoint dies after the 
message is received, which is probably just as bad as the message not 
being delivered...

>> One of the very trippy things about Erlang is that I can be sitting on
>> my PC in England, and I can just tell some Solaris server in Brazil to
>> spawn a new process. As expected, no word on how the hell this
>> actually works. I would have *thought* this means that the necessary
>> code gets beamed over the wire, but some documentation seems to
>> suggest that you have to install it yourself. And yet, you can
>> apparently send arbitrary functions as messages, so...?
>
> The functions are merely references to installed code. The code itself
> has to already be in Brazil, and the function you send is "this module,
> this name, this version".

I did think for a moment that you could send a function this way. Once 
you've compiled your module, every lambda function it contains must 
already exist in the compiled code somewhere. High-order functions make 
the program more complicated, of course. You can't necessarily point to 
just *one* function. It might be that you call several functions to 
construct the final function object. But even that is likely to be more 
compact than the actual executable code.

Thing is, I can open the REPL and type in a function, and then ask to 
spawn that on a remote node. How does *that* work?!

> There's a document out there somewhere that lists the binary formatting
> of the contents of all the messages.

I'll bet. No doubt somebody has written libraries for other languages to 
speak the same wire protocol.

> Starting the process remotely works because the local interpreter has
> (or establishes) a TCP link to the remote machine. All the messages for
> all the processes on your PC go over the same link to the machine in
> Brazil. There isn't one connection per communicating process.

Given that Erlang supports creating billions of processes and there's 
only a few thousand possible TCP ports... yeah, I figured that's how it 
would work.

I notice that connecting one Erlang node to another causes the two nodes 
to connect to *all* the nodes that each endpoint knows about. I'm not 
actually sure why though. You would think that a fully interconnected 
mesh would quickly exhaust the supply of TCP ports too.

>> There's also no word on authentication. I mean, how does the machine
>> on Brazil know that I'm authorised to be executing arbitrary code on it?
>
> Nope, you're fucked.
>
>> In other words, you're authorised if you can
>> actually access the system in the first place.
>
> Yes, exactly.

Ouch.

>> It's a similar story when we come to code hot-swapping. Surprise,
>> surprise, it turns out that you can't just magically hot-swap code,
>> just because it's written in Erlang. No, you have to do *actual work*
>> to make this possible.
>
> Well, sure.

You say "sure", but the documentation sings about how reliable Erlang is 
and makes it sound like all this stuff just happens by magic, because 
Erlang is so wonderful. The truth is actually that Erlang just 
implements the minimal features required to let _you_ build complex, 
robust, distributed applications.

> The two-versions limit is because the code itself isn't GCed. They
> document that they could have GCed the code, but that would have added
> lots of overhead for something relatively rare.

Fair enough.

>> Really, the only help that Erlang is giving
>> you is the ability to easily change from one chunk of code to another.
>> You could probably do the self-same thing in Java, if you built your
>> application so that [...]
>
> You'd have to be able to pass the network connections around too, remember.

You'd have to be able to do a lot of things. It would require careful 
application design, but it doesn't look especially difficult. (To get 
the framework done, anyway. Making the application itself sounds really 
hard, in Java or Erlang.)

>> Erlang really gives you no assistance at all beyond the minimal level
>> of "you can have two modules with the same name". And it's limited to
>> just two, by the way. If you try to load a third version, anything
>> running the first version is unceremoniously killed. And there's no
>> mention of any way to detect whether old code is still running.
>> (Perhaps there is, but I didn't see it mentioned.)
>
> There is, but it's deep in the bowels of "system" stuff.
>
> The OTP system provides all the packaging and maintenance for the higher
> levels of management. Complaining that Erlang itself doesn't automate
> such stuff is like complaining that you need a Linux package manager
> because the file system only knows about files, not applications. :-)

I don't mind which level the assistance is implemented at, just so long 
as there *is* some. ;-)

>> There was some talk of a packaging system, which sounds quite
>> interesting, but obviously no details are described.
>
> It does a bunch of stuff, including giving you ways to roll things
> forward and back and such.

Yeah, that's what I figured. Handling this stuff manually for more than 
a few dozen nodes sounds like the exact opposite of "reliable".

> Realize that most of these "systems" you're talking about are actually
> implemented as Erlang code.

Hey, it worked for Smalltalk. ;-)

> So, for example, to compile code, you
> actually sit down at the REPL and invoke the compiler function.

I wish the Haskell compiler could do this...

> I agree about the syntax etc. It's quite possible a better type system
> would make it easier.

Escaped experiment, eh? ;-)

> You should also check out Hermes, which is essentially the same thing
> with an astoundingly strict and high-level type system.

Heh, there is never enough free time. ;-)

>> Just reading the document, I saw endless examples of data structures
>> who's meaning is ambiguous due to the lack of types. For example, it
>> is apparently impossible to tell the difference between a process that
>> died because of an exception, and a process which merely sent you a
>> message that happens to be a tuple containing the word "EXIT".
>
> Well, it's possible unless you asked "turn all crashes from processes
> I'm watching into EXIT messages."

The advice presumably being that workers should only receive "real" 
messages, and supervisors should only receive exceptions. Then there can 
be no ambiguity. (Until the supervisor's supervisor asks it to do 
something...)

Actually, that's a point. Each process has *one* mailbox. Not one 
mailbox for each process it's communicating with. Just *one*. So you 
have to manually figure out who each message came from - usually by 
having the sender put a reply address in manually.

>> The advice for this presumably being "don't do that".
>
> No, this is actually more like "We did that on purpose in case you want
> the supervisor to react as if you exited while you keep running."

To quote the document, "it's a feature, not a bug!"

>> It's slightly bizarre. Erlang looks for all the world like a crude
>> scripting language with no safety at all and abysmal run-time
>> performance. And yet, the language is designed for running network
>> switches, possibly the most demanding high-performance hard-realtime
>> system imaginable, and people claim it has nine 9s up-time. The only
>> container types are linked lists and tuples, and yet the standard
>> libraries somehow include cryptography and complex network protocols.
>> Very odd...
>
> It's possible to link C (or anything else) into Erlang. The GUI for
> Erlang launches a separate Tcl/TK process and talks over a socket to it
> to do the drawing, for example.

So you're saying that all the "interesting" stuff like the SSL 
implementation is actually just an off-the-shelf C library?

I never did understand what's so great about Tk. It looks horrid, it's 
really hard to use, and it's almost impossible to get the layout you want...

> In general, network switching isn't that hard real-time, because you
> usually have custom hardware to do the switching. A 5ESS (which can
> handle up to 800,000 phone lines) runs on two 6800s, one of which is a
> hot spare. But of course there's a large lump of hardware that's routing
> the individual bytes here and there, so the 6800 only actually gets
> involved when you connect or disconnect, basically.

Wait, a 6800? As in, the chip *before* the obsolete 68000?

Damn. If the actual switching is in hardware, that the heck do you need 
the CPU for at all?

> Erlang is the only language I've found whose syntax sucks worse than
> C++, yet it has way, way less built in than C++.

Erlang: 0wned.

> The primary problem, I think, is it started out as "let's investigate
> how we can write a million lines of code that doesn't crash." Of course,
> once you have that million lines of code and it's running reliably,
> you're not going to go back and fix anything as trivial as the syntax.

Yeah. It's an escaped experiment. And now it's too late. TOO LATE!!! >_<

Actually, one of the reasons I wanted to learn about Erlang was to see 
if I can design something that does the same thing, but isn't insane.

>> By contrast, apparently the general practise with Erlang is to /not/
>> cover cases which aren't expected to occur, and just let the thing
>> throw an exception on a pattern-match failure.
>
> Well, that's not quite true for message pattern matching. For matching a
> pattern against a value, yes. But if you don't read messages, you leave
> them in the input buffer, which then grows and grows until you take out
> the entire interpreter. Normally you'd find the place in your code where
> you *think* you handled all the messages, and you'd put in a "if I match
> anything else, crash out" branch to the pattern match.

Well, yeah, I meant for pattern matching against a value. Receiving 
messages as a bit special.

>> What does that sound like to you? Yes, congratulations, you've just
>> invented object-oriented programming.
>
> Yep. Except since it's actually a separate process, it's called an
> "actor", not an "object". :-)

That's possibly the most irritating thing about the gen_server example. 
It explains what each line of code is doing, but neglects to mention the 
fact that one half of the code is potentially running on another 
continent to the other - which, let's face is, is the entire point of 
making *remote* procedure calls. :-P

>> Of course, modules don't have inheritance.
>
> No. You'd instead write something that invokes functions in a module
> whose name you provided when you instantiated this module. There's
> actually even syntax for this.

Yeah. It's just a tiny bit more work, that's all.

>> This whole "module with special functions" monstrosity is all the more
>> weird because Erlang also has
>>
>> 1. first-class function names
>>
>> 2. lambda functions
>>
>> Um, like, WTF? Why does the code have to go in its own module? Why
>> can't I just pass you a tuple of function names, or even lambda
>> functions, that define the callbacks? Hello??
>
> Because then you can't update the module. One of the things OTP is
> providing for you is the whole infrastructure for updating your module.
> That's why you send the "state" back to OTP and why the code has to be
> in a separate module. When OTP gets the "update" message, it invokes the
> new module with the state.

OK. So that's why the state is explicitly threaded (quite apart from 
Erlang apparently having no other way to handle running state). That 
still doesn't explain why I can't put several related behaviours into a 
single module. Perhaps I *want* to replace them all at the same time.

>> Many things about Erlang still remain unexplained. I still have
>> absolutely no clue WTF an "atom" is.
>
> An atom is an interned string. Same as a "symbol" in LISP or Smalltalk.
> The trick is that in a message, an atom is an index into the atom table,
> so it's short regardless of how long the atom is.

I never really understood what an interned string is. (And hence what 
the difference between a Smalltalk string and a symbol is.)

So you're saying that the compiler replaces all occurrences of an atom 
with a pointer to a single instance of the string? (And hence, you can 
compare atoms for equity by simple pointer equity rather than string 
comparison.)

>> Nor have I discovered what this "OTP" thing that gets mentioned every
>> 3 sentences is.
>
> It's the libraries that take the basic operations Erlang supplies (like
> "load new code" or "spawn link") and turn it into things like
> installable packages.

Right, OK. So what does it mean when they say "the compiler is an OTP 
application"?

>> I have utterly no idea how the "registered process names" thing works.
>
> A process can say "My mailbox is the registered process on server ABC
> for XYZ". Another process can say "Give me XYZ@ABC" and get that
> mailbox. It works by shchlepping non-Erlang messages between the erlang
> processes, just like the keep-alives do.

OK, so, I can register a PID with a particular name. Is that name local 
to just that node? Does every node in the system know about it? How do 
you avoid registering a name that some unrelated application has already 
registered? If I ask for a particular name, how does it figure out which 
node that's on? If a node goes down, does the name get taken out of 
service? How do you handle the same service being available from several 
nodes? (For load-balancing or fault tolerance, perhaps.)

>> Erlang is supposed to be for very high-performance systems,
>
> Not really.

Hmm. It certainly seems to be.

>> Apparently there's a distributed database system written in Erlang,
>> but I didn't find any mention of
>
> Mnesia. They were originally going to call it Amnesia, but someone
> pointed out that naming a database system after an illness characterized
> by forgetting things is probably a bad idea.

The name still looks wonky to me. ;-)

> It's a cute little system, but it has its flaws due to being implemented
> on top of a single-assignment language and keeping everything in memory.

Hmm. Sounds... fun.

> It's basically a distributed transactional system on top of the built-in
> database tables whose TLA name escapes me at the moment.

Right.

> Note that all these things, OTP and Mnesia, along with stuff like the
> compiler, the debugger, the profiler, the package manager, etc etc etc,
> are all documented as "here's the functions you use to do it in Erlang."
> Almost none of these things are actually command-line tools.

Nothing wrong with that. The question is how much detail the 
documentation explains. ;-)


Post a reply to this message

From: Invisible
Subject: Anmesia
Date: 10 Mar 2011 05:59:48
Message: <4d78af24$1@news.povray.org>
>> Apparently there's a distributed database system written in Erlang,
>> but I didn't find any mention of
>
> Mnesia. They were originally going to call it Amnesia, but someone
> pointed out that naming a database system after an illness characterized
> by forgetting things is probably a bad idea.
>
> It's a cute little system, but it has its flaws.

> http://www.erlang.org/doc/man/mnesia.html

An object/relational hybrid "suitable for telecommunications applications"?

It says it has "extremely fast real-time searches", but it also says 
that the query language is "an add-on library"?

Reading the rest of the documentation just made my head hurt. I 
particularly love how a "table" can be "set", "ordered set" or "bag", 
but only the first and last option are explained.

A reference manual is no way to learn how to use a complex system.


Post a reply to this message

From: Darren New
Subject: Re: The mysteries of Erlang
Date: 10 Mar 2011 12:54:52
Message: <4d79106c$1@news.povray.org>
Invisible wrote:
> On 09/03/2011 06:22 PM, Darren New wrote:
>> Having worked with Erlang, here's some comments.
> 
> In what capacity are you "working with Erlang"? 

I'm not, currently.

> While I doubt anyone actually *wants* unreliable software, there can't 
> be too many problems that require distributed programming.

Unless you want to listen to radio stations in several different cities and 
integrate the results. Altho I wound up writing my own stuff for that and 
not using Erlang, that was the experiment.

> Erlang is partially functional, in that functions are apparently 
> first-class, and it's got single-assignment and immutable data 
> structures. 

Functions in C are first class. That doesn't mean it's functional. The 
immutable data structures come from the single-assignment, except where the 
data structures aren't immutable, of which there are several.

> On the other hand, it doesn't enforce referential 
> transparency; you can put side-effects anywhere you like.

Well, that's the killer. To me, if every statement can have side-effects, 
then it's not a functional language. It's just single-assignment. You have 
none of the benefits and all of the draw-backs of a functional language in 
Erlang.

 > The document I
> read claims that it's considered beneficial to limit side-effects to 
> just a few places in the codebase, 

Except that message sending and message receiving are both side-effects as 
is spawning or linking another process, so that works pretty poorly. Plus, 
as I said, having the side-effects means you cannot (for example) use your 
own functions in guards. Essentially, nothing is actually functional in the 
language *except* assignment and a handful of built-ins. (Not even all the 
built-ins, for that matter.)

> the language itself doesn't /enforce/ any kind of strong separation 
> between pure and impure code.

For some reason, the Erlang enthusiasts think the crappy syntax and the 
single-assignment procedures are intentional and beneficial properties of 
the language. Like any niche language, the fanboi's refuse to accept that 
any compromise was made during its design.

>> If it was *actually* functional, you could put your own functions in
>> guards, for example.
> 
> Wait - you *can't* do this already?? o_O
> Why on earth would you design it this way?

Because your own functions aren't referentially transparent, because the 
language isn't actually functional, even at that level. The functions in a 
guard are the ones that *are* functional. Not even all the built-in 
functions can go in guards.

Actually, in the documentation, I think there's a note on each built in 
function that says whether you can use it in a guard or not.

>>> - The system is supposedly insanely reliable. People toss around "nine
>>> 9s up-time" as if this is some sort of experimentally verified *fact*.
>>
>> It is. They've been running a commercial switching network with it for
>> something like 10 years, and one of the six data centers lost power or
>> something and all the machines in it for 20 minutes once, so they had a
>> 15% downtime for 20 minutes out of ten years.
> 
> One data point is scientific fact?

It's a scientific fact that you can achieve nine 9's of uptime over the 
course of a decade with programs written in Erlang, yes.  Pretty sure that's 
really, really hard to do with any other programming language.

> (Presumably both processes have to be running within the same logical 
> "node", but then again also presumably you wouldn't have more than one 
> node on the same box except for test purposes anyway...)

You start up the run-time with a command-line argument that says "Hey, I'm a 
node in a distributed program". Wings3D doesn't do this, so it's not a node.

> As I say, this doesn't really seem to be spelled out anywhere else that 
> I could find, and it seems a rather crucial detail.

It's kind of in all the books and stuff. I think the PhD thesis is presumed 
required reading, if nothing else.

>>> less the same kinds of error handling that any other language provides
>>> - catch/throw, etc.
>>
>> Huh. I didn't even know it provided catch/throw. I've never seen that
>> used, actually.
> 
> Yeah they do. It's in there. Apparently the catch functions turn an 
> exception into a tuple begining with 'EXIT' and followed by some stuff. 
> Using throw you can make catch return an arbitrary data structure. 

Ah, I see. Yes, I had forgotten about that.

> (Quite how you tell the difference between an exception and a normal 
> result is left to the imagination... 

Well, an exception will start with EXIT, while a regular result is unlikely 
to, unless you specifically confuse things that way.

> It looks like you can use throw to 
> make it look like the catch block exited normally.)

Yes. Basically, it lets you return early.

> Right. So it just sends heartbeat messages? (I presume the bit about 
> rebooting the machine is optional; you wouldn't want your PC to reboot 
> just because Wings 3D crashed, for example.)

Yes, it's a separate monitor process. It sends heartbeats as well as watches 
for a couple other simple things.

>>> Message sending has the curious property that message delivery is
>>> /not/ guaranteed, but message ordering /is/ guaranteed. Like, WTF?
>>
>> Message delivery isn't guaranteed because it's asynchronous, and the
>> remote machine may crash between the time you send the message and the
>> time the remote machine receives it. Ordering *is* guaranteed because if
>> the remote machine crashes before it gets message #5, it won't get
>> message #6 either.
> 
> You mean that if message #5 doesn't arrive, message #6 is actually 
> guaranteed not to arrive?

If message #5 didn't arrive because the receiving machine crashed, yes, 
message #6 isn't going to be received either. If message #5 didn't arrive 
because the network switch died, then #6 isn't going to be delivered either.

> TCP guarantees both delivery and ordering. Unless the connection 
> actually breaks. So is that all Erlang is saying? That it if one of the 
> machines catches fire, Erlang can't guarantee that messages will be 
> delivered to it?

Yes, exactly.  More precisely, you can't tell if the message was received 
and processed or not.

> These people are /serious/ about reliability. o_O

Well, yes, that's how you get 5 minutes of downtime over the course of 10 years.

>> Knowing the receiver got the message is as simple as adding code to send
>> an ack each time you handle a message.
> 
> Network protocols 101: It's not that simple. It is possible for the 
> message to arrive, but the acknowledgement itself to fail to be delivered.

If the ack fails to be delivered, it's because the receiving machine (nee 
sender) has crashed and hence doesn't need to see the ack.

Well, not quite. If the network between the machines dies, each machine is 
going to think the other crashed. This makes a mess. Mnesia has all kinds of 
crap in place to recover from this, especially given that you might make 
independent updates to the same table in two places.

> Now, if you're running on TCP, so that ordering and delivery are 
> guaranteed (unless there's no transport or no endpoint), the only way 
> this can happen is if the transport or the endpoint dies after the 
> message is received, which is probably just as bad as the message not 
> being delivered...

Right. The point of their warning is, you have to deal with messages not 
making it one direction or the other. You don't have to deal with the 
messages being out of order. You have to deal with the fact that if you tell 
a remote machine to do something, it might not happen. You don't have to 
deal with the fact that if you tell a remote machine to do two things, it 
might do them in a different order.

> Thing is, I can open the REPL and type in a function, and then ask to 
> spawn that on a remote node. How does *that* work?!

Hmmm. Actually, I think perhaps it *is* willing to ship functions around. 
The details escape me at the moment. Maybe you can send functions but not 
modules? I don't honestly remember.

> I'll bet. No doubt somebody has written libraries for other languages to 
> speak the same wire protocol.

I don't know. Probably, but I'm not sure why you would.

> I notice that connecting one Erlang node to another causes the two nodes 
> to connect to *all* the nodes that each endpoint knows about. 

Yes, unless you tell it not to.

> I'm not actually sure why though. 

Reliability. ;-)

> You would think that a fully interconnected 
> mesh would quickly exhaust the supply of TCP ports too.

Unless you have more than 50,000 machines, unlikely.

>>> In other words, you're authorised if you can
>>> actually access the system in the first place.
>>
>> Yes, exactly.
> 
> Ouch.

One of the OTP functions shows how to authenticate and encrypt the 
connection en Erlang code, IIRC. That's also one of the tutorials.

Imagine a room full of a few thousand processors. One outgoing internet 
connection running thru a firewall that's happy to VPN anything going out to 
a known location, and which drops anything going in or out from an unknown 
IP.  Why encrypt everything?

Actually, I had forgotten. There is a "cookie" you can set, and anyone 
connecting needs the same "cookie". So there is a minimal security level, 
not unlike the similar feature in X servers.

> You say "sure", but the documentation sings about how reliable Erlang is 
> and makes it sound like all this stuff just happens by magic, because 
> Erlang is so wonderful. The truth is actually that Erlang just 
> implements the minimal features required to let _you_ build complex, 
> robust, distributed applications.

What, you're believing fanboi's?

If you want the version that actually *does* magically distribute and make 
reliable your code, you want to read about Hermes, a language designed by Strom.

>> The two-versions limit is because the code itself isn't GCed. They
>> document that they could have GCed the code, but that would have added
>> lots of overhead for something relatively rare.
> 
> Fair enough.

Plus, you should be dealing with the occasional "process suddenly ends for 
no reason" exception robustly anyway. :-)

> You'd have to be able to do a lot of things. It would require careful 
> application design, but it doesn't look especially difficult. (To get 
> the framework done, anyway. Making the application itself sounds really 
> hard, in Java or Erlang.)

Heck. Honestly, you can do it in Ada.  Ada has facilities for declaring some 
library code as capable of being hot loaded and unloaded.

> I don't mind which level the assistance is implemented at, just so long 
> as there *is* some. ;-)

I found it annoying that there's no global roadmap. So you read about 
something and it says "If X happens, a message is sent to the error logger." 
  WTF is the error logger? Where's the documentation. Then you track that 
down, and it's all talking about bits and pieces that are also not 
documented, etc, with the assumption that you have a bunch of people who 
wrote the error logger hanging around to tell you how to find where the 
errors got logged and such.

>> Realize that most of these "systems" you're talking about are actually
>> implemented as Erlang code.
> 
> Hey, it worked for Smalltalk. ;-)

Yeah, basically the same idea, except instead of an "image" you actually 
have a bunch of running processes.

>> I agree about the syntax etc. It's quite possible a better type system
>> would make it easier.
> 
> Escaped experiment, eh? ;-)

That's exactly what it was. Except that by the time it escaped, there were 
millions of lines of production code written in it, so you couldn't even 
reasonably fix it.

>> You should also check out Hermes, which is essentially the same thing
>> with an astoundingly strict and high-level type system.
> 
> Heh, there is never enough free time. ;-)

I don't think the book is available any more, either. I should scan mine.

> The advice presumably being that workers should only receive "real" 
> messages, and supervisors should only receive exceptions. Then there can 
> be no ambiguity. (Until the supervisor's supervisor asks it to do 
> something...)

Well, more like, normal worker processes shouldn't communicate with messages 
that start with the atom "EXIT". It's not that hard. It's like saying "don't 
make closing the TCP connection the confirmation."  There's only one or two 
patterns that the error messages use. You shouldn't use "Close" as the name 
of the routine to invoke when you want your object to keep running, either.

> Actually, that's a point. Each process has *one* mailbox. Not one 
> mailbox for each process it's communicating with. Just *one*. So you 
> have to manually figure out who each message came from - usually by 
> having the sender put a reply address in manually.

Yep.

Hermes gives you as many mailboxes as you want, with as many connections to 
each mailbox as you want, and you can send both connections and mailboxes in 
messages to other processes. So if you want hot-swap code, write your 
processes as being capable of packaging up its mailboxes and sending them to 
a new process.

> So you're saying that all the "interesting" stuff like the SSL 
> implementation is actually just an off-the-shelf C library?

It's possible.

> I never did understand what's so great about Tk. It looks horrid, it's 
> really hard to use, and it's almost impossible to get the layout you 
> want...

Well, no, it's really easy to use, it's easy to get a good-looking layout, 
and it only looks horrid because it looked cutting-edge 10 years ago and 
nobody ever improved the look of it since then. (Altho they've worked on 
that more lately.)

That's exactly why half a dozen non-Tcl languages actually went so far as to 
embed a Tcl interpreter into their code in order to be able to use Tk. I'm 
pretty sure it's not Tk that's at fault here. ;-)

>> In general, network switching isn't that hard real-time, because you
>> usually have custom hardware to do the switching. A 5ESS (which can
>> handle up to 800,000 phone lines) runs on two 6800s, one of which is a
>> hot spare. But of course there's a large lump of hardware that's routing
>> the individual bytes here and there, so the 6800 only actually gets
>> involved when you connect or disconnect, basically.
> 
> Wait, a 6800? As in, the chip *before* the obsolete 68000?

Yes.

> Damn. If the actual switching is in hardware, that the heck do you need 
> the CPU for at all?

For deciding what to connect. You pick up the phone? The 6800 connects your 
phone to the dial-tone generator. It's not sitting there generating the 
waveform in software. You push a button? The 6800 routes your line to the 
DTMF decoder, which signals the 6800 each time you let go of the tone. When 
you've dialed enough numbers, the 6800 figures out what line you're supposed 
to connect to and plugs you through.

> Actually, one of the reasons I wanted to learn about Erlang was to see 
> if I can design something that does the same thing, but isn't insane.

Check out Hermes first. :-)

> Well, yeah, I meant for pattern matching against a value. Receiving 
> messages as a bit special.

Right.

> OK. So that's why the state is explicitly threaded (quite apart from 
> Erlang apparently having no other way to handle running state). That 
> still doesn't explain why I can't put several related behaviours into a 
> single module. Perhaps I *want* to replace them all at the same time.

I don't know. Go work for Erricson. ;-)

>>> Many things about Erlang still remain unexplained. I still have
>>> absolutely no clue WTF an "atom" is.
>>
>> An atom is an interned string. Same as a "symbol" in LISP or Smalltalk.
>> The trick is that in a message, an atom is an index into the atom table,
>> so it's short regardless of how long the atom is.
> 
> I never really understood what an interned string is. (And hence what 
> the difference between a Smalltalk string and a symbol is.)

I explained this for Warp a couple weeks ago. An interned value is a 
reference value where reference equality equals value equality.

Or, in normal speak, an interned string is one where there's only one copy 
of any given string, so you can compare the addresses where two strings are 
stored and tell if they're equal.

In Erlang, you can think of an atom as one element of a giant global enum of 
all the atoms you've ever used in your program. It's nothing more than an 
index into a table that happens to be readable.

> So you're saying that the compiler replaces all occurrences of an atom 
> with a pointer to a single instance of the string? (And hence, you can 
> compare atoms for equity by simple pointer equity rather than string 
> comparison.)

Basically, that's what an interned string is. An "atom" is implemented as an 
interned string, but it's syntactically different. It's ... an atom. It's a 
string treated as a single atomic value. You can't ask for the third 
character of an atom, for example.

>>> Nor have I discovered what this "OTP" thing that gets mentioned every
>>> 3 sentences is.
>>
>> It's the libraries that take the basic operations Erlang supplies (like
>> "load new code" or "spawn link") and turn it into things like
>> installable packages.
> 
> Right, OK. So what does it mean when they say "the compiler is an OTP 
> application"?

It means the compiler is a function in the OTP libraries that you invoke in 
order to compile the code.

> OK, so, I can register a PID with a particular name. Is that name local 
> to just that node? Does every node in the system know about it? How do 
> you avoid registering a name that some unrelated application has already 
> registered? If I ask for a particular name, how does it figure out which 
> node that's on? If a node goes down, does the name get taken out of 
> service? How do you handle the same service being available from several 
> nodes? (For load-balancing or fault tolerance, perhaps.)

I don't remember all this stuff. Registering as a known name passes your 
mailbox and the name to a process that proceeds to distribute it and answer 
queries about it. You can look at the documentation for it to see what it does.

>> It's basically a distributed transactional system on top of the built-in
>> database tables whose TLA name escapes me at the moment.
> 
> Right.

ETS is the name of the sub-system functions.

>> Note that all these things, OTP and Mnesia, along with stuff like the
>> compiler, the debugger, the profiler, the package manager, etc etc etc,
>> are all documented as "here's the functions you use to do it in Erlang."
>> Almost none of these things are actually command-line tools.
> 
> Nothing wrong with that. The question is how much detail the 
> documentation explains. ;-)

The other problem is that all the documentation assumes you've read the 
previous documentation and knows what they're talking about, but it doesn't 
actually give you pointers to any of the previous documentation.

So if you want to use mnesia, you read mnesia, but it assumes you know what 
ets and gen_server are and etc. So mnesia will have a function and say "this 
only works on ram_raw tables" or "that function starts a new server" and 
assume that you know "ram_raw" is something that ets implements (not Mnesia) 
and that a 'server' in this context specifically means a gen_server module.

You'd like mnesia, tho. It's a lot like STM in some ways.

-- 
Darren New, San Diego CA, USA (PST)
  "How did he die?"   "He got shot in the hand."
     "That was fatal?"
          "He was holding a live grenade at the time."


Post a reply to this message

Goto Latest 10 Messages Next 10 Messages >>>

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.