alisinabh

alisinabh

Rust-like Enums in Elixir using macros - Good/Bad?

Hi everyone!

My colleagues and I ran into challenges with atoms and tuples in our Elixir codebases, especially when modeling domain states or structured data clearly.
Atoms are handy but also sometimes annoying. They’re global, easy to typo and need extra tests to ensure correctness. Not to mention converting strings safely to atoms isn’t very straightforward (and sometimes unsafe/unreliable even with String.to_existing_atom/1).

I had an idea to solve this and built a PoC library but to be honest I’m not entirely sure if the community would like it. Especially I’m curious to know if you see this in a codebase would it make understanding the code easier or more difficult?

Here is my PoC: (It is called Enuma and is available on Hex.pm if you want to try it out)

defmodule Shape do
  use Enuma

  defenum do
    item :circle, args: [float()]   # Circle with radius
    item :rectangle, args: [float(), float()]  # Rectangle with width and height
    item :triangle
  end
end

circle = Shape.circle(5.0) # = {:circle, 5.0}

require Shape

case shape do
  # Can use them in pattern matching as they are compile time macros
  Shape.circle(r) -> :math.pi() * r * r
  # Above case clause is same as
  # {:circle, r} -> :math.pi() * r * r
  Shape.rectangle(w, h) -> w * h
  Shape.triangle() -> raise "Triangle Area Not Supported!"
end

Generally what Enuma does it generating macros based on the enum definition. It also generates is_* guards that can be used in “when” clauses.

Back to the question

Here’s the thing: Enuma does slightly alter how you’d typically write Elixir code, which might make some developers uncomfortable or lead to mixed styles in codebases.

I’m curious:

  • Do you think libraries like Enuma enhance clarity, or do they risk complicating Elixir’s simplicity?

  • Would Enuma fit well into your projects, or does it feel too different?

  • How do you currently handle structured domain modeling in your code?

  • If you compare it with Ecto.Enum, would you see useful benefits for your usecases?

I’m looking forward to your thoughts!
https://github.com/alisinabh/enuma/

Most Liked

hauleth

hauleth

Good news for you all my fellow alchemists - it is built in:

defmodule Shape do
  import Record

  defrecord :circle, radius: nil
  defrecord :rectangle, width: nil, height: nil
  defrecord :triangle, []
end

circle = Shape.circle(radius: 5.0) # => {:circle, 5.0}

require Shape

case shape do
  # Can use them in pattern matching as they are compile time macros
  Shape.circle(radius: r) -> :math.pi() * r * r
  Shape.rectangle(width: w, height: h) -> w * h
  Shape.triangle() -> raise "Triangle Area Not Supported!"
end
18
Post #3
garrison

garrison

I think this is a problem which is best solved by a type system. TypeScript’s string literal types solve a similar issue. Once you have type inference/checking for atoms the problems you describe should go away.

D4no0

D4no0

I am not sure you should do that to begin with. What is the use-case where you are required to convert strings to atoms on a basis?

I’m not a big fan of using functions generated by macros in application code, it doesn’t play well with LSP and as the modules become more complex, it becomes a source of confusion. Maybe you could redesign the code to work around modules:

circle = Enuma.new(Shape, :circle, [5.0])

case shape do
  Enuma.match(Shape, :circle, [radius]) -> ....
end

The way I did it a few times when I required enums was with functions:

def circle(radius) when is_float(radius), do: {:circle, radius}

case shape do
  {:circle, radius} -> ....
end

While the macro version looks more organized, I think that simple functions are superior as you don’t tie your enum functions to a module, it amounts to easier refactors and you can cram a unlimited number of functions in a module. I am not a big fan of how functionality like defstruct couples with the module, but I am not entirely sure at the same time how a readable alternative would look like.

As it currently stands, it seems too alien for most of the codebases I worked with. Nonetheless, I think such a library might be a great addition to the ecosystem.

Where Next?

Popular in Discussions Top

PragTob
Hello everyone, I know we had quite some threads (read through lots of them) about background job processing but it remains a hotly deba...
New
New
thojanssens1
It would be nice to be able to define a redirect from one route to another from the router.ex file. E.g.: redirect "/", UserController, ...
New
pillaiindu
In django there is a cache framework backed by memcached. Rails also puts a lot of emphasis on caching, and even the idea of russian-doll...
New
Qqwy
Looking at the stacks that existing large companies have used, WhatsApp internally uses Mnesia to store the messages, while Discord uses ...
New
WildYorkies
It seems that the more I read, the more I find Elixir users speaking about all the ways that Elixir is not good for x, y, and z use cases...
New
crispinb
On reading dhh’s latest The One Person Framework it strikes me that Phoenix with LiveView is already pretty much this. However, never hav...
New
nburkley
AWS re:Invent is on at the moment with some interesting announcements. One new feature in particular is the Lambda Runtime API for AWS La...
New
AstonJ
I’ve just started the Phoenix part of the utterly brilliant online course by @pragdave. On generating the Phoenix app he uses the --no-ec...
New
paulanthonywilson
I like Umbrella projects and pretty much always use them for personal Elixir stuff, especially Nerves things. But I don’t think this is ...
New

Other popular topics Top

New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement