apoorv-2204
How do you handle enums in your codebase?
The Issue of Enums in Elixir
Elixir doesn’t have a native enum construct, so we usually rely on atoms, strings, or macros to represent named constants.
Observed Issues
String vs Atom:
Some contexts (like pattern matching or internal logic) prefer atoms, while others (like database fields or API responses) require strings.
Maintaining consistency across both can be cumbersome — I eventually prefer enums as strings for simplicity.
Compile-Time vs Runtime Checks:
We can emulate enums using function calls, but not within guards or compile-time contexts.
Macros can provide compile-time validation, but require the require keyword, which reduces simplicity.
There’s no built-in mechanism for enforcing enum constraints at both compile and runtime.
Questions:
How do you handle enums in your codebase?
Do you rely purely on atoms and type specs, or use macro-based libraries for safer compile-time checking?
Would a lightweight, language-level enum-like abstraction make sense for Elixir?
Most Liked
mudasobwa
defmodule MyEnum do
defmacro sigil_MYENUM({:<<>>, _, [value]}, [] = _modifiers) do
case __CALLER__.context do
:match ->
if value in ~w[one two], do: value, else: raise(value)
_ ->
if value in ~w[one two], do: value, else: raise(value)
end
end
end
defmodule TestMyEnum do
import MyEnum
def test(value) do
IO.puts(~MYENUM[one])
IO.puts(~MYENUM[two])
case value do
~MYENUM[one] -> IO.puts("ONE MATCHED")
~MYENUM[two] -> IO.puts("TWO MATCHED")
end
~MYENUM[three]
end
end
This example is a bit overfilled with some shenanigans, but it’d give you a great start.
garrison
Actually, one thing that does bother me is that there are a few different ways to implement constants and they all have different tradeoffs. I think this is a bit unintuitive.
@my_enum [:first, :second, :third]
def my_enum, do: [:first, :second, :third]
defmacro my_enum, do: [:first, :second, :third]
The third one is the only way to do pattern matching on constants from another module, yet that is the one which is not documented.
garrison
Of course for database fields Ecto does support enums. For other stuff I generally use atoms and pattern matching. I wouldn’t necessarily be opposed to a native Enum construct but pattern matching and a public constant for enumeration gets you pretty much all of the way there.
I think the type system will help a lot, here (and Dialyzer already helps a bit). I always liked the string literal types in TS. We generally use atoms, but it’s the same idea. Types are most useful for tooling rather than type-checking (i.e. autocomplete).







