Hacker News new | ask | show | jobs
by Jtsummers 3726 days ago
> even strongly typed.

A nit: Erlang is strongly typed. You cannot (ok, excluding numbers) almost transparently convert a string into a number into a tuple like you could in C, to which everything is merely memory so casting allows trivial but potentially erroneous conversions. Erlang is dynamically typed, not statically typed. Dialyzer + type annotations (and some inferred) allows for static analysis, but it's not directly part of the compiler so it can't be strictly enforced (automatically).

EDIT:

Also, consider that erlang processes are a dual of objects in the OO sense. They can receive various messages and respond by changing state and/or transmitting messages. This interaction is similar to the way objects (instances of classes) behave in OO languages. The difference being that they're all running concurrently. So you could put a massive amount of state into one process. OR you could have a handful of processes that act together like you have a handful of classes in an OO language.

If I write a server for playing a game, I don't include all of a player's state and their connection in one process. I have a process that handles the network connection. It sends messages to a process (or processes) that handle player state. Which connect to some game state processes, and on until the network handler sends messages back to the player or gets more messages from the player.

3 comments

You've hit on the tradeoff though. By decentralizing your state, you've increased your inter-process synchronization requirements. In the worst case everything ends up being tightly bound and your application runs like a single threaded application because everything is always blocked waiting for the state update from a remote thread.

Huge parallelism is easy if your data and processes are largely independent, but the real world is rarely so kind.

> By decentralizing your state, you've increased your inter-process synchronization requirements.

But you've also increased your reliabily as well. Who cares if the tight single threaded application with a shared heap cand handle 100K connections, if as soon as one of those connection leads to a segfault, all the other 99999 crash as well.

A single Erlang server can handle 2M connections[1].

[1] https://blog.whatsapp.com/196/1-million-is-so-2011?

> In the worst case everything ends up being tightly bound (...)

Your tradeoff is between “tightly bound by shared data structures” vs. “tightly bound by process synchronization”. I don't see how either is better than the other.

> the real world is rarely so kind

In the real world, from what I've seen, while everything is interconnected to everything else, not all the connections are equally strong or important. If you want to compute exact results, without possibility of failure, no matter what the computational cost, then sure, you need to take all the connections into account. If you can trade some accuracy for performance gains in the average case, you'll probably want to find ways to prevent minor failures from bringing down the entire system.

Erlang processes are objects in their own right, not "dual" to them.

The dual of object types (records of methods) are sum types: Object types are defined by how you can eliminate them (calling a method on an object), whereas sum types are defined by how you can introduce them (applying a constructor to suitable arguments).

He probably means that an Erlang process is equivalent to an object and message passing among processes is equivalent to method calls.

In my experience with Elixir, objects, method calls and mutable data are easier to write than processes, messages and immutable data. It's not the mutable and immutable part, it's more about the boiler plate of spawning processes, receiving messages and matching them to dispatch them to the appropriate functions. Ruby's and Python's class and method definitions are much more compact and less error prone. Unfortunately they are not as good at parallelism. I wonder if it could be possible to have an OO language with an automatic Erlang process per object, automatic immutable data (just don't let reassign values to variables, like Erlang), and a way to define supervisor trees.

There is Pony (http://www.ponylang.org).
Interesting language, thanks. What's the amount of adoption to date?
Close to 0%. It's still in development. I would love to see this development into a high performance alternative / complement to erlang.
Yes. In the same sense that closures and objects are equivalent.

http://c2.com/cgi/wiki?ClosuresAndObjectsAreEquivalent

Or use LFE - Lisp Flavoured Erlang by one of the designers of Erlang, Robert Virding. All the BEAM/OTP goodness with the meta-programming and homoiconicity of Lisp. Worth checking out.
>> I wonder if it could be possible to have an OO language with an automatic Erlang process per object, automatic immutable data (just don't let reassign values to variables, like Erlang), and a way to define supervisor trees.

Is it possible to implement it using the metaprogramming features in Elixir?

> "(ok, excluding numbers)"

That's a pretty big exclusion. There are times one really doesn't want type promotion from a particular number representation for performance or accuracy reasons, and if your dynamic typing system does not allow contracts to reject numerical types that will implicitly promote, you can run into huge performance problems.

An example: suppose you want to divide a list of numerators by a single denominator producing a new list. Each division is not guaranteed to produce an whole number. What's the type of the resulting list? What is the type of each member of the resulting list? What is the type of a sum of the resulting list? Is the per-division cost roughly constant or does it depend on the particular numerator, denominator pair?

Bear in mind not just differences between integer and real numbers, but that real numbers may be exact or inexact, and that there may be various representations of inexact numbers (from native floating point to various types of bigfloat).

Really strict typing -- whether in a dynamically typed language with contracts or similar mostly runtime mechanisms, or in a statically typed language with type checking at compile time -- forces you to deal with these questions explicitly. In this case, you might specify that the source list of divisors is a list of positive integers, and that each division produces a single floating point result (and thus you wind up with a list of floats; and you'd specify that the sum of the resulting list should be a float too). That sacrifices precision for performance, which will tend to matter depending on the content and length of the source list.

There are plenty of dynamically typed languages that get fiddly trying to avoid turning some or all of the operations into much more precise types than single float (or even doing exact representations, rational number style), and the performance impact can be dramatic.

Conversely, you may not want to lose precision as you move away from +-0.0f, so you may want to specify that exact arithmetic will be used in the operations in this example instead.

Because Erlang is a dynamically typed language, in your example, dividing a list of numerators can give you any type back. It could be a list of numbers, integers, floats, tuples, strings. It might not even be a list at all. This is just something you deal with in dynamically typed languages. You're making a case for statically typed languages, which is fine. But that's not Erlang (or Python, or Ruby etc. etc.).

Erlang doesn't have contracts, but it has pattern matching. You can write a function in Erlang that is guaranteed to return only a list of integers let's say, by either converting them, or not accepting lists of floats in later part of your code. On top of this, you have Dialyzer which can warn you when your code doesn't do this (if you typed your functions correctly).

There are quite a few dynamically typed languages which let you restrict the numeric types that polymorphic operators can consume and produce. A long heritage of that is in Common Lisp (see the Type Specifiers section in CLtL) and several other Lisp-family languages allow this too (e.g. Racket, which has a pretty substantial contracts sytem).

So it's not new. It doesn't make Lisp any less dynamically typed as a language, and it is wholly optional. Newer dynamic languages also let one do some partial or "gradual" typing where a programmer wants to use it.

There is a cost to this at function calling time, but then there is also a cost if one manually programs in a check on the type of an argument, for instance. Good compilers, however, can prove that functions that aren't of (or exported to) global scope will never be called with anything other than the specified types, and will omit the type-checking code.

Additionally, there is plenty of research into interoperation between code in statically typed languages and dynamically typed ones.

Naturally you can always convert back after your arithmetic operator produces the wrong type. But that can be expensive in itself, and it hurts more when the arithmetic operator could have performed a much cheaper operation.

A way around this of course is to eventually de-polymorphize the potentially-expensive operator and program in a hopefully cheap type-check by hand, in write-generically-first/optimize-(or even make correct)-after fashion.