tim2CF
Hello!
Does anyone know why Elixir structures are implemented on top of Erlang maps, but not on top of Erlang records (tuples)? For me, Erlang records are looking more natural then maps for basement of Elixir structs, and besides in some cases it seems performance of records is better (for example in pattern matching), take a look:
iex(2)> defmodule Hello do
...(2)> defstruct [:foo]
...(2)> end
{:module, Hello,
<<70, 79, 82, 49, 0, 0, 5, 144, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 182,
0, 0, 0, 18, 12, 69, 108, 105, 120, 105, 114, 46, 72, 101, 108, 108, 111, 8,
95, 95, 105, 110, 102, 111, 95, 95, 7, ...>>, %Hello{foo: nil}}
iex(3)> struct = %Hello{foo: 123}
%Hello{foo: 123}
iex(4)> fn -> 1..1000000 |> Enum.each(fn(_) -> %Hello{foo: foo} = struct; foo end) end |> :timer.tc
{13719674, :ok}
iex(5)> record = {Hello, 123}
{Hello, 123}
iex(6)> fn -> 1..1000000 |> Enum.each(fn(_) -> {Hello, foo} = record; foo end) end |> :timer.tc
{565886, :ok}
iex(7)>
Of course, you can say - “If you like Erlang records, just use it”, but Elixir infrastructure is very coupled with structs/maps - they are used everywhere (Ecto, Phoenix, Plug, Elixir standard libraries). So if I’m using Elixir, I don’t really have a choice - I have to use structs/maps
Trending in Discussions
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
New
I want to open this thread for you all to discuss and help those who really like Ash but are still hesitant to use it in a real project. ...
New
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using.
We’re particularly inte...
New
I’m posting this in response to Jose’s recent tweet (Cr. link) :
People are sleeping on Elixir for a coding harness:
Hot-code swappi...
New
Hello,
I wrote Stop My Hand, a Scattergories-like web application using Phoenix/LiveView as my learning project for Elixir (after readin...
New
AcmeScript — Writing JS hooks as if I were still using Elixir
I’ve been having fun building a little something over the last few days: Ac...
New
Other Trending Topics
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
There are three potential reasons for members of this forum to have a look at https://vutuv.de
You are tired or annoyed of LinkedIn.
Yo...
New
Aludel - LLM Evaluation Workbench
Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #elixirconf-us
- #ai
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
kokolegorille
There is a good answer from Jose here about the choice of struct vs record.
michalmuskala
Please, please, don’t benchmark in the shell. The shell runs an interpreter and not the compiled code. The result will probably will be wildly different.
OvermindDL1
Indeed, here it is in Benchee, I tried to prevent certain optimizations from happening by interning the test data in different modules than that which is accessed so things like the record macro’s don’t get optimized out and so forth (records were a lot faster than structs before I made that change).
Code
struct_record_bench.exs:Results:
So Records are faster in general (in all tested cases here actually) than Structs, but only marginally so, so much so that only the most performance sensitive code would really care, so in general most people shouldn’t care.
I’m surprised that putting the struct type in the update syntax doesn’t make it faster actually as it could infer some existing structure, but I guess that all it would be doing is adding an extra runtime check or so (hence the module-optional variants are slower in the end).
EDIT: Personal Opinion time: Personally I’d prefer records were ubiquitous and used struct syntax (first class records in other words). Records in every language I’ve seen are statically sized, there is no point in them being maps, especially if they ‘own’ their module definition as structs do now then all the proper accessors for Access and extra data would all be accessible as they are for structs as well and as such by using those generated macro’s then you could generate getting/setting code that would be even more efficient than how structs work now. HOWEVER, Elixir is extremely poorly typed and doesn’t know what the type of a given thing would be, and Erlang works around that by requiring using the record name at all uses of a record variable, Elixir tries to be a little more succinct, and that succinctness is at odds with efficiency, and so the first-class syntax uses the slightly less efficient version in order for ease of use and relegates the more efficient version to a side set of macro’s since you require the names anyway. If Elixir had a decent typing system then you’d be able to have both efficiency and succinctness, but maybe that’s for an Elixir 2.0 or something. ^.^
EDIT: Hmm, a possible workaround for the first-class syntax access would be just dispatching based on the ‘module’ in the type-tag of the record, it would be a ‘remote call’ on the BEAM but might be good… I should test…
Qqwy
Great benchmark!
In practice, wouldn’t the datastructure’s internals usually be read/patternmatched/created/modified from within the same module that defines it? So is preventing these optimizations justified?
OvermindDL1
In some cases sure, in those cases though then records are even much faster than structs (still not enough for ‘most’ code to care though).
OvermindDL1
I went ahead and decided to test a more ‘direct’ record interface, I added a new Record interface to emulate the back-end of if a front-end syntax were used (the start of it anyway, it could be more fleshed out).
The
struct_record_bench.exsfile now:Essentially the ‘stock’ is the Elixir stock record interface, the ‘direct’ is what an optimized type-aware setup would do (ala requiring the use of the record ‘name’ in the usages, like in Erlang), and the ‘remote’ is what a dynamic dispatch interface would look (so something like elixir’s existing struct syntax with no known type information). And the results:
So first of all, the stock and direct should be identical if I apply all optimizations to the direct version as stock already has, even without it (a single opcode change from the looks of it, meh right now, close enough) it’s still almost identical. As for
directit is doing a remote (I.E. slow) module function call (ah if only we had tuple calls still to save the uglyelem’ing stuff on the call, then we really could “right now” have a native looking syntax, but tuple calls got broke in the latest OTP version, permanently…), so it should be slower in general and so it is. Some of these calls were slower on older BEAM’s so that is probably why map’s were picked when maps came out for ‘structs’ but nowadays I’m not sure if there is a reason to having picked maps over tagged tuples for records as in the traditional cases you still put the type for 'put’ing%StructName{..|..}as well as could easily come up with something similar for optimized ‘get’ call). You still have unknown-record fallback capabilities as well with this even if a touch slower.Still, it was a fun test, I really do wish Elixir Structs were tagged tuples underneath as it would make working in the Erlang ecosystem easier. ^.^
michalmuskala
The primary advantage of structs over records is readability. I can take any struct, print it anytime and it will be readable. With records, you can easily get an unreadable pile of nested tuples - it’s a huge pain when working with Erlang APIs. Another huge disadvantage of records is during upgrades - changing the record structure correctly is extremely hard when you have the “new” modules with the new definition of the records that need to handle the old records. For that reason, I know that some Erlang codebases don’t use records at all, but rather prefer proplists, which are a poor-man’s version of maps.
Qqwy
To be honest, I think the readability of map-based structs is greatly improved because of Elixir’s syntactic sugar that turns
%{a: 1, b: 2, __struct__: Foo}into%Foo{a: 1, b: 2}. But of course even without that you are able to see the names of the different fields; is this what you mean?As for upgrading: This is definitely a place where map-based structs are better than tuple-based records!
OvermindDL1
That’s why you have an
inspectfor them. Unlike the older Elixir days pre-consolidation (and my ProtocolEx can handle tagged tuples fine and faster than Elixir’s Protocols) the protocol can know precisely what is supported and what is not and such a default implementation could even by specified by thedefbetterrecordcall itself (or via an option in case they want to override it, or add an__inspect__method in the module as a fallback or whatever, lots of options).With my above defined BRecords module you could easily have upgrade functions specified inside it then protocol dispatch an upgrade path along them (and other things if needed), or just call the functions straight to upgrade them. You have to do similar things to maps and structs anyway as data formats change, a string might need to become a list in whatever field, etc… etc… It is good to reify those to a specific area and just pass in the needed upgrade information as always. I never had issues with that all in Erlang.
I never actually ran across a library that used proplists as their ‘state’ store, records or trivial values I’ve always seen. That sounds like a very bad way to handle the code and would make dialyzer typing it a bit more irritating as well (though with such codebases I wonder if they used dialyzer at all).
You could have identical syntax for records though, that exact same
%Foo{a: 1, b: 2}could easily generate aFoorecord.Still unsure about that, with maps it’s easy to keep old useless data polluting it, more irritating to Dialyze, takes up more space, takes longer to update unless it’s truly huge, and I still think it is a bad BAD idea to upgrade state in-place, you should always decompose an old version and build a new version just to make sure dialyzer helps catch issues (which it would not otherwise on upgrades since it ‘assumes’ the old version types would be the new already unless you explicitly override it), to make sure you don’t miss something (with a map you can forget a key pretty easily, not so with records), among other things.
All of this would of course be far far more sensible and direct if Elixir actually had a half decent type system, dynamic typing is a horror and is the reason why this is an issue at all… >.<
tim2CF
Totally agree, and I hope at least one of these projects will be usable someday..
https://github.com/josefs/Gradualizer
https://github.com/alpaca-lang/alpaca
https://github.com/wende/elchemy
Just off-topic comment because you mentioned type systems