
Big changes to Erlang - waffle_ss
http://joearms.github.io/2014/02/01/big-changes-to-erlang.html
======
lkrubner
I love this:

"Key := Val Updates an existing Key The key must be present in the old map. If
Key is not in the old map Erlang will complain loudly. Using two operations
instead of one has several advantages: A spelling mistake cannot accidentally
introduce a new key"

Lord knows how many times I accidentally created a new key with a spelling
typo, and then later when I wanted to retrieve the data, I was like "What the
hell is wrong? I know I put data in that key, so why is nothing coming back? I
guess I have discovered a rare bug in the compiler that no one has ever
discovered before."

~~~
rdtsc
I also like how such rather large changes are being introduced in a language
that has been in production for many decades. That is pretty remarkable.

~~~
Uchikoma
I don't want to sound pessimistic, but adding a map structure to an language
sounds rather small to me.

~~~
rdtsc
To a new and experimental language it would be a small change. Try adding a
built-in map to C or Java. Lambda expressions took year to add to Java for
example.

Now if say Nimrod or Dart acquired built-in sets (I don't know maybe it
already does) it wouldn't be too remarkable. They are pretty experimental.

Also there is a difference between having a library and a language built-in.
All those languages including Erlang, have libraries that provide associative
data structures.

~~~
NigelTufnel
I don't understand. By built-in map you mean "map literals"?

~~~
rdtsc
Yes, as built-ins I consider literals (with specific language syntax). Well,
otherwise most modern useful languages have at least a library level
implementation of some associative data structures. Erlang has them too, dict,
gb_tree and a few others.

------
urbit
"This trick is well known to old-style functional programmers, they waffle on
about Y combinators and eat this stuff for breakfast, but it’s the kind of
stuff that gives functional programming a bad name. Try explaining this to
first year students who had a heavy night out at the pub the evening before."

Priceless (and completely true). I really think this attitude is why Erlang is
such a success.

~~~
thirsteh
I think there's definitely a rule against dismissing Y combinators on
news.ycombinator.com.

~~~
derefr
They're important in CS theory, sure, but you sure don't want to encounter one
in raw form lurking in a regular codebase. (Just like you wouldn't want to
encounter monadic IO in raw >>= form.)

~~~
maxiepoo
Using the y combinator outside of some ridiculously abstract code would be
irritating. Using (>>=) makes sense sometimes though. For instance if you just
wanted to echo something you could do

    
    
      do stuff <- getLine
         putStrLn stuff
    

but the stuff variable is then unnecessarily introduced. You could instead
just write

    
    
      getLine >>= putStrLn
    

or the flipped version if you want it to read more like function application

    
    
      putStrLn =<< getLine

~~~
logicallee
how do neither of you get the obvious joke OP is making with "I think there's
definitely a rule against dismissing Y combinators on news.ycombinator.com".
(he's making a joke out of the fact that YCombinator is named after the
construct.)

~~~
buu700
That wasn't a joke.. People have been hellbanned for speaking ill of Y
combinators.

~~~
eruditely
Really?

~~~
thirsteh
Maybe that was the joke.

------
gordonguthrie
I am in the process of writing an OTP-ish dialect of Erlang that compiles to
Javascript and has its own runtime environment
([http://luvv.ie/mission.html](http://luvv.ie/mission.html)) so maps is a more
work for me, so I am a bit yay!/sigh!...

The thing that I am less clear about with maps is their relationship with
Mnesia tables going forward. Mnesia tables take their schema from records. I
suspect we will go down the route of having records that contain 'privileged'
fields (effectively ones you can have indexes on) and field that contain maps
which allow to 'extend' the schema without performing difficult state
transforms.

It will certainly help when you need to add a field to a record and thread
that change through a running system without stopping it.

~~~
rdtsc
Hey, luvvie looks awesome. Thanks for all your great work. I am following it.
Sorry don't have much insight as to what happens with Mnesia. Just wanted to
thank you for your work.

~~~
gordonguthrie
Cool, just slogging away, its is still early doors...

------
portmanteaufu
Whoa. This makes the language feel much more approachable. I love that they've
provided both an "upsert" and a semantically distinct "update" for their maps.
Working without upsert is painful, but the problem of accidentally inserting
when you meant to update is an irritating pitfall. They've really picked a
good middle way here.

Does anyone know how often Learn You Some Erlang is updated? I'd definitely
take another pass at the language with these features in place. Kudos, team!

~~~
mononcqc
Author here. I haven't yet updated it, but plan to eventually update the
website with these features. The thing is a lot of it won't need to change
majorly.

Maps should be a replacement of data structures like dicts and gb_trees, but I
personally do not see them as a replacement of records within a module, where
I feel their restrictiveness is welcome, for two main reasons:

1\. especially to crash early in live code upgrades, despite, I'm sure, a lot
of people disagreeing with me.

2\. The module isolation inherent to records makes people think at a protocol
level and with their API much, much better than the common pattern of saying
"screw it", sharing the state around, and breaking abstraction all over. I
like how it constrains the programmer to think of what should be passed around
in messages, and that maps may remove that "think hard" part of the problem.

Maps should be especially nice and enable more complex dictionary
manipulations, nested key/val mapping, and so on, and in terseness of
operations. More elegantly, they could be a decent fix to 'use ETS to optimize
K/V operations', although they won't benefit from the same parallel access.

I plan to explain this and possibly revisit some code snippets from the book
in an add-on chapter, and also show what I wouldn't change.

Regarding Funs, I probably will just add a section to the anonymous function
part, and see if I ever used recursive anoynmous functions before. It's likely
that I avoided them on purpose in the book and as such, won't need to add too
much there.

Let me know if that sounds good to you.

~~~
colanderman
_Maps should be a replacement of data structures like dicts and gb_trees_

Why? Why do you need to pattern-match on a structure whose keys you don't know
_a priori_? Why should the syntax prefer one map-like data structure over
another?

Don't get me wrong, I love that maps are in the language now – _as a
replacement for records_ , where syntax and pattern matching make sense and
are used all the time, and the data structure issue is much less sensitive.
Please _don 't_ hamper their performance by forcing them to support the use
case for which dicts and gb_trees are intended!

~~~
mononcqc
Maps literally have a module definition that ends up being similar to dicts if
you want to swap them in directly.

You can know a data structure's keys in advance while still having it dynamic
and not a record: proplists, key/val lists, and so on are like that. The user
has to know the keys to use them sometimes. The data structure definition
doesn't.

The weakness of dicts and gb_trees is two-faced: they're both slower than maps
promise to be, and they do not have pattern matching.

Records and maps are distinct in my opinion. I, for example, won't expect to
be able to use Dialyzer and do type checking of specific key's values (much
like with dicts, trees, k/v lists), but will do so with records.

Maps are pretty much dicts and trees with pattern matching added more than
anything. They even have the same issues with considering the equality of
integers and floats.

Records still have their use case as is right now. Maps have more in common
semantically with dicts than they have with records, where the similarity is
syntactical. I prefer to prioritize semantics over syntax.

~~~
colanderman
_Maps literally have a module definition that ends up being similar to dicts
if you want to swap them in directly._

So? Having a similar interface doesn't mean they have comparable
implementations. Even in the maps runtime implementation, we see a benefit of
knowing that a structure is homogeneous: run-time type information can be
lifted out of the structure, saving memory and time.

 _You can know a data structure 's keys in advance while still having it
dynamic and not a record: proplists,_

proplists are like, the poster child _static_ heterogeneous structure,
precisely because I _do_ know all the keys in advance! It's just that in a
given instantiation, some of them map to the value "undefined"!

 _The weakness of dicts and gb_trees is two-faced: they 're both slower than
maps promise to be_

Then reimplement the _dict_ module. How does syntax help here?

 _and they do not have pattern matching._

Again, why do you need to pattern match something whose keys are determined at
runtime? I've never wanted such an ability in a language; I don't see the
benefit.

 _Records and maps are distinct in my opinion. I, for example, won 't expect
to be able to use Dialyzer and do type checking of specific key's values (much
like with dicts, trees, k/v lists), but will do so with records._

Why not? OCaml can perform typechecking of structurally typed values. That's a
solved problem. If there's no plans to support it in Dialyzer I'll add it
myself.

 _Maps have more in common semantically with dicts than they have with
records, where the similarity is syntactical._

I think we disagree on what a "map" is. You say a map is one thing (a
replacement for a key-value store), but when I see syntax that looks, walks,
and quacks like a structurally typed record, I see a structurally typed
record, and I'm utterly baffled why those things should have anything to do
with each other.

~~~
mononcqc
You know that maps as envisioned in the EEP
([http://www.erlang.org/eeps/eep-0043.html](http://www.erlang.org/eeps/eep-0043.html))
should let you do pattern matching using variables, right? It's not there
_now_ because it's a provisional implementation, but it will be there.

We will be able to do X = 3, #{X := Val} = Map, ... to extract the value of
'Val' under the unknown key 'X'. This just hasn't been implemented _yet_ , but
is part of the total specification.

What you see is the _incomplete_ implementation of maps, not up to the total
spec, because this is a RC1 implementation. They will support part of the
record use case, but the entirety of the dict use case with more flexibility
there (including comprehensions, from_list, etc.)

They are, by specification and once complete, going to be far more feature-
equivalent with dicts than records.

~~~
colanderman
_We will be able to do X = 3, #{X := Val} = Map, ... to extract the value of
'Val' under the unknown key 'X'._

Yes. I don't understand the use case for that.

~~~
lostcolony
I think you may be reading too much into it. The goal is to be able to replace
dict/orddict it seems like. For that, we need some way to be able to get
values out for keys that are unknown at compile time (and pattern matching is
a lovely way of doing that).

That is, let's say I have an in memory map of IDs to counters (for whatever
reason).

I can write an implementation to increment and get the counter value for a
given ID (assuming this is a gen_server and this is the call implementation)
like so (well, possibly like so. I haven't used maps yet; haven't looked at
the early r17 builds yet) -

    
    
      handle_call{{incrementAndGet, Id}, _, State) ->
        #{Id := Val} = State, 
        NewVal = Val + 1,
        {reply, NewVal, State#{Id := NewVal}}.
    

The comparable implementation right now, using a dict/orddict, might look like
-

    
    
      handle_call{{incrementAndGet, Id}, _, State) ->
        Val = orddict:fetch(Id, State),
        NewVal = Val + 1,
        {reply, NewVal, orddict:store(Id, NewVal, State)}.
    

TL;DR - Do you need to be able to get out a value, given a key? Yes. How do
you read out values given a key? Pattern match, as described.

PS - Because it pattern matches, if I decided I didn't want to throw if the
thing doesn't exist, I can do cool stuff like -

    
    
      handle_call{{incrementAndGet, Id}, _, State) ->
        NewVal = case State of
          #{Id := Val} -> Val + 1;
          _ -> 1
        end,
        {reply, NewVal, State#{Id => NewVal}.

~~~
colanderman

        #{Id := Val} = State
    
        Val = orddict:fetch(Id, State)
    

Sorry, I don't see the benefit of one over the other. I _do_ know that one
ties me to a specific implementation of a map that may or may not work well
for my data if I use it, and that merely by existing, prevents O'Keefe's
frames from being implemented in an optimized fashion.

~~~
lostcolony
Okay, so what you meant was not that you don't see a use case, but rather, you
don't see why they implemented maps as a replacement to dict/orddict, and in
lieu of frames.

Those are different questions, the latter of which not one I feel capable of
answering, given that I wasn't involved with the discussions.

I assume the reason for wanting to replace dict/orddict is because they feel
very much a second class citizen in the language (see at bottom for a
comparison of why pattern matching trumps separate modules), dicts are
inefficient for small collections, and orddicts are inefficient for anything
but small collections.

As to why they went this route instead of frames, I recall seeing it in the
discussions (I believe the Erlang mailing list but I can't recall), but I
never paid that much attention to it.

But then, to the first, why not just keep the dict/orddict syntax, a
completely frivolous example, solely created to demonstrate why pattern
matching is cleaner then dict/orddict -

    
    
      %This just increments a map if the password passed in matches, sets the counter to one and the password to the passed in password if the map does not contain a password, and throws if the password does not match, but does exist.
      incrementAndGetIfAllowed(Map, Password) ->  
        case Map of
          #{password := Password, count := Count} -> 
            {Count + 1, Map#{count := Count +1};
          #{password = _} -> throw(bad_password_can_not_increment);
          _ -> {1, Map#{count => 1, password => Password}
        end.
    
    
      %We have to extract first, then do our logic separately. Behaves the same as the prior function.
      incrementAndGetIfAllowed(OrdDict, Password) ->
        CachedPassword = orddict:find(password, OrdDict),
        CachedCount = orddict:find(count, OrdDict),
        case {CachedCount, CachedPassword} of
          {{ok, Count}, {ok, Password}} -> 
            {Count + 1, orddict:store(count, Count + 1, OrdDict)};
          {_, {ok, _}} -> throw(bad_password_can_not_increment); 
          _ -> 
            orddict:store(password, Password, 
              orddict:store(count, Count + 1, OrdDict))
        end.
    
    
    

And that's just with a map/dict holding two things. If you have complicated
logic based on the internals of the map/dict, it could remain pretty elegantly
matched upon with the map, while getting exponentially uglier using a
dict/orddict.

~~~
colanderman
The example you give uses no feature that frames don't have.

To demonstrate why maps need syntax support, you need an example which
pattern-matches _keys_.

------
Groxx
> _If we now build a large list of objects, all with the same set of keys,
> then they can share a single key descriptor. This means that asymptotically
> maps will be as efficient in terms of storage as records.

Credit for this idea comes from Richard O'Keefe who pointed this out years
ago. I don’t think any other programming language does this, but I might be
wrong._

That is a nice language feature to enforce, I love it :) This is however
basically the same thing that V8 / most Javascript runtimes do under the
hood[1]. Essentially, as you construct objects and add/remove keys, you back
them in the engine with more-efficient shared objects that have the same
structure. Add a key D and it moves from <shared structure with A,B,C> to
<shared structure with A,B,C,D>. Since most code ends up doing the same kind
of operations on a bunch of similar bits of data, you can save a lot of
compute-time by assuming that and having a slower fallback when it fails (like
using an actual dictionary instead of a Struct-like thing).

That said, Javascript has zero enforcement for this, and the runtimes may have
already shifted to something different. They are quite different beasts. Just
pointing out that the idea has been around.

[1] it has been a while since I've looked closely, and I may be mistaken. Call
it 95% certainty.

------
Cushman
These are great changes, and a reminder that I must find a reason to write
more Erlang.

For the record, one of these is another thing JavaScript got right, sort of,
with named function expressions:

    
    
      setTimeout(function innerName() {
        if (shouldRecurse())
          innerName();
      });
      typeof innerName; // undefined
    

I don't think I've ever seen anyone actually use this in production code, of
course. And you're free to have your own issues with function declarations and
named function expressions having the exact same syntax, which causes a big
problem in IE<=8 that is probably responsible for this feature not being in
common use (and omitted from CoffeeScript, alas).

~~~
masklinn
You can also do that in Clojure, since you'd usually self-recurse with (recur)
and a function will create a recursion point.

    
    
        (set-timeout (fn []
          (if (should-recurse)
            (recur)))

~~~
Morgawr
You can also name fns in clojure

(map (fn whatever [a] (println a)) [1 2 3])

ps: this code is stupid but I'm too lazy to write useful clojure from my
phone, you get the gist

------
sdegutis
It seems odd that we haven't standardized on a name for
maps/tables/dictionaries/associative-arrays/hashes.

~~~
kevinmchugh
And in JS, it's an object.

Figuring out how to store arbitrary key-value pairs should be one of the first
steps in learning a new language, given that every language has a different
name for it.

Associative Array is probably my least favorite name for it, since it implies
arrays aren't associative. It was a clojure book that first made me realize an
array is a map that only allows ints for keys.

~~~
jaredsohn
> an array is a map that only allows ints for keys

Actually, it only allows nonnegative ints for keys.

Also, it has the added undesirable property of requiring memory proportional
to the largest key value, rather than number of keys.

~~~
justincormack
Not if it is Fortran.

------
kibwen
As someone who's completely foreign to Erlang, can anyone explain the
rationale behind replacing records with maps? At a first glance it seems as
though records are nominal types and maps are structural types, and if true
that would suggest to me that these are complementary features rather than
supplementary ones.

~~~
masklinn
> As someone who's completely foreign to Erlang, can anyone explain the
> rationale behind replacing records with maps?

Simplicity and flexibility. Erlang records have to be declared and they're
nothing more than syntactic sugar for tuples.

> At a first glance it seems as though records are nominal types

They're not, not in the way you'd usually expect anyway. They're little more
than macros for accessing tuple fields by name:

    
    
        -module(test).
    
        -record(foo, {bar, baz}).
    
        main(_) ->
            R = #foo{bar=1, baz=2},
            io:format("~w~n", [R]).
    

will print:

    
    
        {foo,1,2}
    

the record information does not exist at runtime. In fact, you can just create
the corresponding tuple and tell Erlang to interpret it as a record:

    
    
        S = {foo, 2, 3},
        T = S#foo{bar=3},
        io:format("~w ~w~n", [S, T]).
    

will generate no warning from either erlang itself or dialyzer, and will print

    
    
        {foo,2,3} {foo,3,3}
    

And although dialyzer (erlang's "type checker") can use records as type specs,
it will be updated to handle map specs[0], allowing for the exact same static
expressivity since you can define a map with explicitly specified keys e.g.

    
    
        -type foo() :: #{ 'status' => 'update' | 'keep', 'c' => integer() }
    

[0]
[http://www.erlang.org/eeps/eep-0043.html](http://www.erlang.org/eeps/eep-0043.html)
section "Dialyzer and Type specification", sadly not directly linkable.

~~~
lucaspiller
Having the record not exist at runtime can lead to issues if you serialise you
records using term_to_binary. If the format of the record changes, old records
can no longer be accessed.

    
    
        1> rd(person, {name, email}).
        person
        2> P = #person{name = 'John', email = 'john@example.com'}.
        #person{name = 'John',email = 'john@example.com'}
        3> P#person.email.
        'john@example.com'
        4> rd(person, {name, email, age}).
        person
        5> P#person.email.
        ** exception error: {badrecord,person}

~~~
mononcqc
That is actually good in my opinion, because of how static records are
expected to be in structure. You shouldn't be able to reload or proceed with
data dating from an earlier version as if nothing happened.

You have to think about what changed and prepare for an upgrade from any
version you may encounter, rather than move along with incorrect data, similar
to if it were corrupted.

I fear Erlang programmers will use maps in a way that allows them to be
sloppy, rather than just exploiting the flexibility they allow. Double-edged
sword, in a way.

~~~
gordonguthrie
I suspect what you will find is that API's are defined with records, one or
more of whose fields is a map. You do kind of do that with Mnesia tables where
you want to specify the indexable-keys up front, but need to have the
flexibility to extend the schema - a nice kv list does the trick.

------
kabdib
The templating of the key descriptors is neat, but not original. NewtonScript
did this in the early 90s (mostly in response to the severe memory pressure on
the Apple Newton). I believe that Self did it, too (but those guys were on Sun
workstations, and just showing off as far as I'm concerned :-) )

------
leakybucket
I've got a passing interest in Erlang, and also saw the existence of Elixir
[http://elixir-lang.org/](http://elixir-lang.org/) , which uses the Erlang VM.
Could someone from either language's community comment on if they see a strong
future for Elixir?

~~~
lostcolony
Well, how about Joe's thoughts? - [http://joearms.github.io/2013/05/31/a-week-
with-elixir.html](http://joearms.github.io/2013/05/31/a-week-with-elixir.html)

------
NDizzle
This is great. I can attack the few chapters I skipped over in Joe's most
recent book[1] that required R17.

[1]: [http://pragprog.com/book/jaerlang2/programming-
erlang](http://pragprog.com/book/jaerlang2/programming-erlang)

~~~
fidotron
I have to say, this is one of my favourite programming books, even though I've
never been paid to write any Erlang at all. Just the insights into how to go
about building reliable systems are inspiring and profoundly affected my work
in other languages.

That and it's written in a brilliantly approachable style.

~~~
Shish2k
> Just the insights into how to go about building reliable systems are
> inspiring

I found that too; I wonder if the programming community would be better off if
more people were pushed towards Erlang? Similar to how every programmer should
know C, so that when they write python they have some idea what's happening
under the hood, it would be good for every programmer to know Erlang/OTP so
they learn how large-scale reliable systems should work?

~~~
rdtsc
I agree. It is just useful to have the ideas and paradigms used in one's
toolbox. Concurrent isolated units working together by sending messages. That
is interesting and can be implemented in other systems. Like say using a
message broker like RabbitMQ or 0mq and isolated processes. Or the idea of
supervision. Have a supervisors OS process watch and restart its workers and
so on. Those can be implemented and applied without even touching Erlang
itself.

------
riquito
The EEP (Erlang Enhancement Proposal) about maps is really neat and a superb
documentation.

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

~~~
minikomi
This was a great read .. A very considered document.

------
talklittle
EEP-43 [0] standards draft, with an extensive overview of new Maps features,
plenty of examples, and discussion on a few different syntax proposals they
had.

(Is there any newer official documentation for maps? I couldn't find
anything.)

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

------
pointernil
Kudos to all ppl involved! Thanks for the effort.

Compared with maps adding a fun-call chaining operator like |> should be
child's play, right? ;)

Seriously, what are the functional programmer's tricks to handle long function
call chains in languages without the |> or similar operators?

Anyway, maps are a more important addition to the environment for sure. So
thanks again.

~~~
nox_
An F#-like operator |> would be useless without a convenient partial
application syntax. Macro-like operators aren't well received in Erlang,
certainly not when they obfuscate what really is going on.

~~~
pointernil
really useless?

[http://joearms.github.io/2013/05/31/a-week-with-
elixir.html](http://joearms.github.io/2013/05/31/a-week-with-elixir.html) ->
"The Pipe operator"

I really get several arguments against such "syntax sugar". I would not call
it __useless __, though.

So how to handle/structure/avoid code with function call chains in absence of
such operators?

~~~
nox_
First, I am never against adding good syntax into a language, I implemented
the named funs. Second, just because Joe says something doesn't mean I agree.
Third, here is my tentative answer to the piping syntax:

An F#-like pipe operator:

    
    
        X |> F.
        F(X).
    

A partial app syntax:

    
    
        fun map(F)/1.
        begin _1 = F, fun (_2) -> map(_1, _2) end.
    

These coupled together allows concise yet explicit function chaining and makes
use of the most common convention which is to pass the most-changing argument
last.

The problem with what Joe likes is that in X |> foo(bar), the call foo(bar) is
transformed to foo(X, bar), making what really happens less obvious, a big no-
no for Erlang.

~~~
pointernil
Kudos for the named funs.

Care to provide some "marketing" about what they allow for? (besides and in
addition to what is already in the eep-0037(?))

Why are they important? How will they support developers in writing and
understanding erlang code? Do they provide even some internal/resp performance
benefits?

Reg. the piping syntax: how is your proposition making the "transformation"
more obvious for the reader of some application code?

To me it seams partial appl. is the more powerful feature for sure. Together
with a rather "simple" |> operator it could provide the fun piping
functionality. It looks like a map function is needed to wrap other functions
taking part in the piping, right? Is that the "make it obvious" part you
mentioned?

The piping syntax from elixir (from the joe blog above) on the other hand
binds the |> operator to a more specialized, less flexible "fun call"
transformation providing fun piping __only __, right?

Anyways, thanks for insights and again really kudos for the named funs.

~~~
nox_
With my two orthogonal syntaxes, there is no transformation. "|>" doesn't need
to look at its operands to be implemented. With a macro, you have a call to
foo/N end up a call to foo/N+1.

Also, coming from a functional background, where I read "X |> F()", I think
"(F())(X)", not "F(X)".

------
oinksoft
Thanks to Anthony Ramine for his hard work on the named `fun' feature, and to
the OTP guys as always :^)

~~~
nox_
You're welcome.

------
sbierwagen

      code {background: #F7F7F9; color: #999999}
    

Egad, the color scheme he uses in the code blocks is horrible. The snook.ca
contrast calculator says that's only a 2.66:1 contrast ratio, which is
terribly unreadable. It needs to be at least 7:1.

(CSS edited for clarity)

------
colanderman
There's been lots of chatter about maps on the mailing list; but the named
inline functions thing is totally out of the blue to me, and totally welcome.
Erlang does such a great job of fixing everything that OCaml almost but didn't
quite get right.

~~~
nox_
Implementor here. Richard O'Keefe deserves all the credit as he had the idea
and suggested the syntax (EEP 37). I just noticed that it was easy to
implement it through let rec expressions in Core Erlang. No VM changes were
required.

------
brickcap
Maps look really interesting. They can also be saved in mnesia right?

~~~
jlouis
Yes, and passed via distribution

------
nodivbyzero
Way to go, Erlang! As a developer, I really appreciate it.

------
varg
Great! Maps looks really nice :) Will mnesia be extended to us maps as an
alternative to records?

------
dipthegeezer
Brilliant Erlang gets even better.

