[erlang-questions] Best Practice in Map keys: Atoms or Binaries (or Strings)?

Vans S vans_163@REDACTED
Sat Oct 1 22:57:55 CEST 2016

I am not comparing implementation details but abstract concepts, what is the purpose of such a type.  The underlying purpose of records and Elixir Structs are fairly similar, the main
difference is that Elixir early on had huge problems with the way records are implemented in Erlang (and still problems exist) so it was hard to port over Erlang records into Elixir (same reason why Erlang macros cannot be ported cleanly). Thus they created Structs which fit into Elixirs way of doing things much better and solve the same problem records solve.  You should not be using records in Elixir unless you want headache.

As I defined in brackets, I mean the general OOP definition.  The general definition of OO is objects DEFINE how you manipulate data.  For example you have a bike object with the turn_pedals function, which mutates data belonging to the bike object such as its position in the world after the pedals turned.  

IMO the correct way to manipulate data is to EXPRESS your intention. A poor example of this is you have functions that take data and return data. 

turn_pedals(BikeSpeed, RiderStrength) ->
    {ok, 2}.

You pass the speed and strength to the function.  Now perform the calculations and return the distance moved.  Now you are responsible for doing something knowing that if the the rider with this strength were to turn the bikes pedals, it would move 2 units. This way your structure does not pollute the expression.  Should the structure change, your expression will still be perfectly valid and working.

The hard and painful way IMO is like this:

turn_pedals(BikeStruct, RiderStruct) ->
   {ok, NewBikeStruct, NewRiderStruct}.

My point is that the second example is the OOP approach, where you change data defined as objects.  The first approach is more functional where there is no relationship to what happens inside the expression to the object defining the data.

The example is bad because it does not cover the case of when the Bike or Rider objectified data changes.  But this is a ongoing exercise with many correct approaches.  I find it works to keep functions that pull out data and write data to the objectified data all in 1 module.  So if you change the structure of the objectification, you wont have to go and hunt down references in 20+ source files. 

I think this answers the last question as well.


    On Saturday, October 1, 2016 2:33 PM, Uniaika <uniaika@REDACTED> wrote:

 There are two things I don't understand in your message:

The first is that Elixir would be backed, or at least similar to
Erlang's records, which isn't quite true, since Elixir's Structs are
built with maps. Maybe you saw something deep in the source code that
made you understand the true nature of structs, but for the moment I'm
keeping the definition provided by the official documentation.

The second thing is the so-called OO nature of Structs. You see them as
objects. If I refer to a definition given by Joe Armstrong (who will
maybe forgive me to bring his article in this context, or maybe not), an
OO object is a cluster of data structures and functions.

So now I'm asking you: How do you manage to access functions from a data
structure that doesn't contain any? I'd be more than happy to be
revealed the great truth beyond the truth concerning structs.

On 10/01/2016 06:39 PM, Vans S wrote:
> That is bad design and advice IMO.  Structs in elixir define object
> orientation, functional programming is about data, everything is data.
>  The tuple being a core building block.
> When you define your application using structures, you have just added
> an extra layer of inference.  Now in most cases that extra layer will
> just get in your way, providing no reasonable benefit.
> One of the biggest enlightenments and problems Erlang developers learn
> early on is the record.  First the record is pretty much a Struct in
> Elixir, with some insignificant differences.  Now most developers would
> do this;  define rigid records like 'User', 'Location', 'Job' with
> default values and  start writing expressions to work with them.  Before
> the developer knows it, the record is being used throughout multiple
> modules across the entire code base. Everything is unit tested and
> static analysis reports the code is 100% excellent, not a single flaw.
> Now the developers code is running in production and its working great! 
> But now a problem comes along, the job the code does has changed,
> storing the email_verification_token inside the 'User' record is no
> longer valid, and a new record is created called 'Validation' that
> houses email_verification_token with sms tokens and other validations.
>  Sure no problem, well defined structure is awesome!
> The entire code base was written rigidly following the spec defined by
> the records (Struct), as soon as we want to make a change now multiple
> modules need to be rewritten.  Now this is not a problem, a day can be
> expent to rewrite/replace 20 modules that use that record. Done,
> rewritten!  
> Now the developers code is running in production and its failing :(
> Turns out by simply replacing text in 20 modules something was missed
> that is producing undefined behavior now. The project is rolled back in
> production to the previous version and the entire development process
> starts all over, trying to figure out where the bug is that was
> introduced from the simple record change.
> Tl; Dr:  Never rely on records (Elixir Structs) across multiple modules,
> always write expressions that manipulate DATA, never write expressions
> that manipulate OBJECTS (oop definition).
> On the count of maps anything used as a key is optimal, there is no
> limitations. Maps are a great flexibility and there is no one right way
> to use them. An example of a useful map key:
> DataChannelLookup#{}
> maps:put({peer1, peer2}, Channel, DataChannelLookup)
> maps:put({peer2, peer1}, Channel, DataChannelLookup)
> maps:put({peer2, peer3}, Channel2, DataChannelLookup)
> maps:put({peer3, peer2}, Channel2, DataChannelLookup)
> Channel2 = maps:get({peer2, peer3}, DataChannelLookup)
erlang-questions mailing list

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20161001/5c82dd8d/attachment.htm>

More information about the erlang-questions mailing list