Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Elixir Gotchas (pragtob.wordpress.com)
105 points by lobo_tuerto on June 26, 2024 | hide | past | favorite | 35 comments


This would be more helpful if it dove into the Erlang side, as many of these "gotchas" are due to how Erlang and the BEAM work, and Elixir, being built on top of it and binary-compatible with the entire ecosystem, necessarily exhibits these behaviours.

Keyword lists, charlists vs strings (actually called binaries) [1], empty map matching all maps are obvious if you look at how they work in Erlang.

I haven't written a single line of Erlang though I can grok it pretty well by now. Knowing the entire platform is how you become a productive Elixir programmer, though it seems some devs refuse to look behind the curtains to understand the Erlang side of it, thus never truly understand the power of the BEAM platform.

---

1: I admit I still unsure about the separation of charlists and binaries. As far as I understand it, binaries are stored in their own heap to avoid copying when sending across processes, so are ideal for sharing large payloads, and charlists are not great for UTF-8 content, as they simply are a naive list of integers (hence one of the gotchas). Erlang uses charlists a lot for "string" content.

Elixir made the right choice to use binaries for strings, as they can ensure they are correctly encoded, provide high-level API to deal with the intricacies of Unicode, and still retain compatibility with Erlang by providing charlists and raw binaries for non-UTF-8 data.


> Knowing the entire platform is how you become a productive Elixir programmer, though it seems some devs refuse to look behind the curtains to understand the Erlang side of it, thus never truly understand the power of the BEAM platform.

This should be a lesson learned by all that boost about Scala, Kotlin, Clojure, F#, C and C++ replacements, JavaScript replacement, without bothering to learn the underlying infrastructure they are build upon.

Many times the X is going to replace Y advocacy, is a bit hard to achieve when the platform was designed for Y and will never go away from Y, for its whole lifetime.


Agreed, even with f# a first class citizen in the .net world, written, maintained and distributed by Microsoft, I still have needed to gain a working knowledge of c# to properly interact with the .net libs and third party libs (which I absolutely need to do as the f# ecosystem is tiny compared to c#). So at the end of the day I needed to learn two languages to atain my goal of learning one enough to be useful.

Disclaimer - I am still learning both - I am no expert on either - and I am still strugling!

(Calling erlang from elixir much eaiser!)


Elixir is peculiar in this regard because Elixir adherents outweigh erlang adherents maybe 10:1.

I really wish that someone would magically transpile all of ERTS so the documentation syntax was uniform.

Elixir is almost more of a guest syntax than it is a diff language.


Everyone should read https://learnyousomeerlang.com


Then, when you have code in production, https://www.erlang-in-anger.com/ (from the same author)


+1 I'd say it's a great read even if you don't plan to do much Erlang or Elixir programming.


exactly


Also, elixir team decided to deprecate single-quotes for charlists. No more single quotes (charlist, only for erlang interop) vs double quotes (binary string, used everywhere in elixir) surprises


This is slightly out of date:

> The other mystery here are the 2 different syntaxes in use for charlists – single quotes ('charlist') vs. the ~c"charlist" sigil. That’s a rather recent development, it was changed in Elixir 1.15, after some discussion … > > So, it’s now less confusing but still confusing – which is why it made this list.

Charlists with single quotes are deprecated since Elixir 1.17 - the ~c[…] sigil should always be preferred.

So hopefully that will reduce the confusion!


you can also use access methods on structs in 1.17 so you don't need the Access.key hack with get_in


I'm not sure this is true? Or I misinterpret.

    Erlang/OTP 27 [erts-15.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]
    Interactive Elixir (1.17.1) - press Ctrl+C to exit (type h() ENTER for help)
    iex(1)> defmodule X, do: defstruct [:a]
    {:module, X,
     <<70, 79, 82, 49, 0, 0, 7, 128, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 244,
       0, 0, 0, 22, 8, 69, 108, 105, 120, 105, 114, 46, 88, 8, 95, 95, 105, 110,
       102, 111, 95, 95, 10, 97, 116, 116, 114, ...>>, %X{a: nil}}
    iex(2)> x = %X{a: 1}
    %X{a: 1}
    iex(3)> x[:a]
    ** (UndefinedFunctionError) function X.fetch/2 is undefined (X does not implement the Access behaviour

    You can use the "struct.field" syntax to access struct fields. You can also use Access.key!/1 to access struct fields dynamically inside get_in/put_in/update_in)
        X.fetch(%X{a: 1}, :a)
        (elixir 1.17.1) lib/access.ex:322: Access.get/3
        iex:7: (file)
    iex(4)> get_in(x, [:a])
    ** (UndefinedFunctionError) function X.fetch/2 is undefined (X does not implement the Access behaviour

    You can use the "struct.field" syntax to access struct fields. You can also use Access.key!/1 to access struct fields dynamically inside get_in/put_in/update_in)
        X.fetch(%X{a: 1}, :a)
        (elixir 1.17.1) lib/access.ex:322: Access.get/3
        iex:7: (file)
    iex(5)> get_in(x, [Access.key!(:a)])
    1


    get_in(x.a)
better example would be nested.

    get_in(x.a.b.c)


Are these even gotchas if they are all very well documented in the official documentation already? I was expecting to see some unusual or undocumented behaviours...


They are gotchas because they're unusual and people unfamiliar with the language will get confused by them.

I'm sure everyone who has used Elixir has been bitten by the list of numbers to string conversion.


They are I've been asked about several of them from beginners.


Unless you are fine letting old homebrew work its "magic" - the installation is too lengthy and involved for a new language. It should just be click, download:

https://elixir-lang.org/install.html

That is the biggest gotcha for me. There doesn't seem to be a simple installer or binary you just get up and running unlike other major languages. Is it a distribution issue with erlang or something? I'd have thought it'd be easy to package it up.


I use asdf - https://asdf-vm.com/ - brew sucks if you need fine control of versions, multiple versions, etc.


Blame your package manager, not Elixir? Its one command to install on Arch for example.


Not requiring a package manager is the point. Python, node, Zig etc supply binaries or a binary installer for each major OS, so why not Elixir? I can't see how it'd be a bad thing for anyone.


> so why not Elixir?

I imagine that part is slightly complicated by the fact that it requires host language runtime (Erlang). Elixir compiled with one version of OTP is not necessarily compatible with another version so supporting binaries for N versions of Elixir becomes NxM problem when you factor in OTP versions, plus supporting distribution of OTP binaries is probably complicated in itself. If there is a problem with OTP, users would swarm first party distributor, but their ability to help would be very limited.


Elixir also doesn't have arrays so you have to fall back on Erlang's extremely clunky variant or, worse, shoehorn a map.


many functional languages (if not all) do not have arrays (intended as a contiguous areas of memory containing indexable elements of fixed size) they are usually left as an implementation as libraries (i.e. Haskell's Data.Array)

There are also easy workarounds to that (including NIFs), I personally never needed arrays in Elixir


I've only ran into a single instance where I needed array-like access and since it was a read only type of thing tuples worked perfectly.


Yeah, just (learn how to) use lists and you'll be fine.

Of course if your goal is to re-implement sorting algorithms that were designed for mutable memory, you will be disappointed.

This isn't a deal-breaker. If you want a platform language, use that. But for web, you'd be hard-pressed to find something better suited than Elixir.


There are arrays - https://www.erlang.org/doc/apps/stdlib/array.html

Just not used across the language


They are trees, so not O(1) access or update, more like O(logn), with some memory fragmentation for every update, insertion or deletion.

Tuples and binaries give O(1) read access, so depending on your use case, they may be better.

Also look at zippers, finger trees and ropes for more dynamic use, especially local operations (cursors).


Not disagreeing with you, but it's worth noting that tuples support random O(n) access by index. They can therefore be used in place of arrays for some simple applications.


Tuples can be up to 16,777,215 elements, so quite substantial applications.

https://www.erlang.org/doc/system/system_limits

There is also some hope that future versions of the Erlang compiler will allow mutable O(1) update of tuple elements through setelement (code inside a process is - almost - guaranteed not to have shared references).

  Safe destructive update of tuples has been implemented
  in the compiler and runtime system. This allows the VM to
  update tuples in-place when it is safe to do so, thus 
  improving performance by doing less copying but also by 
  producing less garbage.
https://github.com/erlang/otp/releases?q=27&expanded=true

It should be possible to add Elixir syntactic sugar to make this more concise and natural.


> random O(n) access

O(1) for reading I believe. The slightly awkward array module is built on top of tuples, a 10-tree of tuples? or something like it.

But good point, once in a while I see list_to_tuple to provide faster indexed reads on lists.


Are you sure you mean O(n) instead of O(1), which I would expect for an array?


Oops! Yes, O(1). Too late to edit, unfortunately.


Besides arrays existing in Erlang, there's a nice elixir library if you need it:

https://www.erlang.org/doc/apps/stdlib/array.html

https://hexdocs.pm/arrays/Arrays.html

Also, if it's read heavy, you can use use tuples for O(1) access.

Besides all that, the community is extremely helpful, if you have questions check out the forum, for example on this very topic there's quite a bit of helpful discussion

https://elixirforum.com/t/elixir-array-like-data-structure/2...


>>> or, worse, shoehorn a map.

That's exactly what JavaScript does (as I understand it), but it hides it from you. And the implementation authors have optimized it over the years.

https://stackoverflow.com/questions/5048371/are-javascript-a...


That is the curse of guest languages, either they have to deal with leaky abstractions, or have to create their own, slower, implementation on top of platform primitives.

Also why with time, I learned to stay with platform languages, even if they aren't as shinny, or have warts that in hindsight could have been done better.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: