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

Fl4m3Ph03n1x
Background This question comes mainly from my ignorance. Today is Black Friday, one of my favorite days of the year to buy books. One boo...
New
ricklove
I was just introduced to Elixir and Phoenix. I was told about the 2 million websocket test that was done 2 years ago. From my research, t...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
mbenatti
Following https://github.com/tbrand/which_is_the_fastest |> https://raw.githubusercontent.com/tbrand/which_is_the_fastest/master/imgs...
New
tmbb
This is a post to discuss the new Phoenix LiveView functionality. From Chris’s talk, it appears that they generate all HTML on the serve...
342 18146 126
New
PragTob
Hey everyone, this has been brewing in my head some time and it came up again while reading Adopting Elixir. GenServers, supervisors et...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
hazardfn
I suppose this question is effectively hackney vs. ibrowse but we are at a point in our project where we have to make a choice between th...
New
und0ck3d
Hello everyone! A few days ago I’ve created a topic here about how people were creating CMSs with Elixir and Phoenix. I’ve been studying...
New
tomekowal
Hey guys! I want to create a toy project that shows a chart of temperature over time and updates every 5 seconds. I feel LiveView is per...
New

Other popular topics Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36128 110
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New

We're in Beta

About us Mission Statement