Structs in erlang

Yeah, but I’ve also experienced the inverse with just plain ole maps too. Except with plain ole maps the situation can be made worse in that some one can alter the shape by injecting arbitrary key/values, which can lead to strange yet serious problems in a distributed system.

I think ultimately there’s only one solution to these problems, and even they can be circumvented, but they are neither here nor now ya know?

1 Like

It is not an issue with structs per-se. The issue is to assume that two fields with the same name share the same meaning. The same can happen when using maps. The same would be possible in the frames proposal.

In fact, I would say this thread misrepresents structs in Elixir. The desired functionality is actually achievable with maps. However, many resist migrating from records to maps because you lose compile-time checking of field names. That’s why structs come up, but that’s not why they exist. See next.


I actually think structs are “overused” in Elixir. The main goal of structs in Elixir is to be nominal, which is used both for protocol dispatching and the exception handling system. Given Erlang’s exception system won’t/shouldn’t change (especially as of EEP 54), I don’t see a reason for structs without protocols. :slight_smile:

However, a lot of people use structs because it provides validation of the field names at compile-time. If Erlang/Elixir had other reliable mechanisms for validating the field names at compile-time when creating/matching maps (such as a type system), structs would be used less frequently because “verified maps” would be enough, so I would focus on tackling that instead.


As per the issue in this thread, I agree with @nzok. Defining an accessor function is the way to go:

temperature(#frobnitz{temperature = X}) → X;
temperature(#snarkle{temperature = X}) → X.

However, what if you want to allow other records to be added here? You can’t without modifying the original module. That’s the goal behind structs+protocols in Elixir: you can define an accessor (actually, any behaviour) for any struct, at any time.

2 Likes

I do agree with this, but people will abuse anything :slight_smile:

Well yes, if I that existed or could exist, I wouldn’t have bothered with discussing this :wink:

1 Like

that’s why I wanted named spaced atoms, just like qualified keywords in clojure :upside_down_face:

2 Likes

Haha. That’s what I mean though: we don’t want structs, we want “verified maps”. :slight_smile:

It is important to highlight that structs are nominal. With that in mind, I disagree with your conclusion.

For data versioning, a potential issue with nominal systems is that the name can refer to different structures over time. However, I would say this is a flaw in the data versioning, not in the names: you shouldn’t make destructive changes to your schema. This is the lesson everyone running a database in production or any over-the-wire serialization library will tell.

Unfortunately it still does not solve the problem that leads to structs being overused, which is that folks want compile-time validation of those keys, namespaced or not.

1 Like

I agree, my point is more on what people expects to work.
let’s say i’m working with pure maps, although a key with the same name can mean different things overtime, if i’m pattern matching exclusively on a set of particular keys, i’m explicitly telling what shape of data I expect. I think overall data shapes are better than named entities.

Sure, but named spaced atoms would help dealing with a key having a singular meaning through your app. That can make nominal systems a less interesting solution. It would have another set of issues when abused, for sure. there are no consequenceless choices on programming :pensive:

1 Like

So true :roll_eyes:
But that is precisely why we should think twice before opening new avenues for abuse, or adding more things to be abused :wink:

3 Likes

That is a fair point. That said, I value all the conversation in this thread. I didn’t necessarily think this would result in consensus or an EEP, but sometimes a weird idea leads to a useful conversation and a good idea. I like the sound of verified maps, have no idea where’d we even begin to tackle that :thinking:

2 Likes

I like the sound of verified maps, have no idea where’d we even begin to tackle that

Maybe from strict specs/typing and dialyzer?

2 Likes

I’m not sure dialyzer is the place for that. I’m not sure it isn’t either :smile:

-module(hmm).

-type this() :: #{this => atom(), that => atom()}.

-export([eh/0, foo/1, bar/0]).

-spec eh() -> this().
eh() ->
  #{this => that, that => that}.


bar() ->
    foo(eh()).

foo(This) ->
    This#{foo => bar}.

I guess the question is does tracking the the instance of this() without spec hints go out of scope for dialyzer? My gut says yes, but I would defer to type theorists and the like to answer that question.

Edit:

To note… this is a case where bar and foo should have specs, but let your imagination run wild as to why a module in some code base may have ended up like this :partying_face:

1 Like

Just to clarify what I meant:

  • If we add a new structure to Erlang then DON’T call them structs, it will just cause confusion.
  • Add completely compatible Elixir structs to Erlang to make interfacing between the languages easier. They may be “overused” but they are still there. It is not difficult to do.
3 Likes

Makes sense. A new data structure I think would not be great with every other thing going on, tough sell :slight_smile: Thus why I was originally thinking about how to build a table on top of a table, for good or bad.

1 Like

I don’t know Elixir, so I may be misunderstanding things.

From the discussion above, the main two things missing in maps for this usecase are performance and having the keys checked at compile-time.

EqWAlizer (which was recently open-sourced: GitHub - WhatsApp/eqwalizer: A type-checker for Erlang) can track the shapes of maps, which as far as I can tell would solve most of the compile-time checking desires listed above.

As for performance, inline caching is the traditional answer to having fast maps in a dynamic language. The idea is to have as much sharing as possible of the Keys tuple, so that {x => 1, y => 2} and {x => 3, y => 4} both point to the same {x, y} tuple. Then record for each code location that access a map the last value of that tuple pointer seen, and the corresponding offset. And finally, whenever accessing a map, first check whether the Keys tuple pointer match the recorded one, and if it does then go straight to the right offset without searching through the map. As a bonus, with an optimizing JIT it is possible to eliminate redundant checks of that keys pointer.
It may sound complicated (and it is much easier to explain on a whiteboard), but it works exceptionally well even for languages like JS where every object is effectively a map.

So I believe that as long as typechecking existing maps is good enough, this new type is not required. What could make a new type required is if a nominal type is wanted (such that maps that have the same keys by accident don’t have the same type).

4 Likes

Dialyzer and eqwalizer will both currently catch this in general and do support most of what’s sought after (so it’s really already there in some respects), the rub is cases like the one posted above. The nice thing about structs in elixir is the check does not depend on an external tool and will catch such a case (unless I go really out of my way to evade it, which I can, but it has to be quite intentional).

Yes, seems like it would, but if you look at the example I posted, it like dialyzer does not handle such a case, yet :smile:

Yup, makes sense. I suppose what you’re suggesting builds upon the value sharing that’s already in place? : https://www.erlang.org/doc/efficiency_guide/maps.html

Also, wouldn’t a key difference between maps in erlang and “maps” in other languages be the underlying datastructure. As an example, in perl a hash is backed by a hash table, whereas in erlang it’s a sorted list up until we hit > 32 keys, at which point it becomes a HAMT… ?

1 Like