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!
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!
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.
Long live records!
It would be nice if records became a concrete thing in Erlang.
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!
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.
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.
Maybe I'm being pessimistic, but I think frames have pretty much zero chance of being implemented so long as maps have syntax support.
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.
Yes. I don't understand the use case for 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}. #{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.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.To demonstrate why maps need syntax support, you need an example which pattern-matches keys.