Best way to implement "enum" like python, c, java?

Spent some time working on @kip’s solution and ended up with

defmodule AEnum do
  defmacro defenum({:__aliases__, _, [name_atom]} = fullname, args, do: block)
  when is_list(args) do
    function_name = String.to_atom String.downcase Atom.to_string(name_atom)
    max = Enum.max(Enum.map args, fn {_,y} -> y end )
    quote do
      defmodule unquote(fullname) do
        defstruct value: 0

        unquote_splicing(Enum.map args, fn {variable, value} -> quote do
            def unquote(function_name)(unquote(variable)), do: %__MODULE__{value: unquote(value)}
            def unquote(function_name)(unquote(Atom.to_string(variable))), do: %__MODULE__{value: unquote(value)}
          end
        end)
        @doc"""
        This is the catch all clause - it just returns nil.
        """
        def unquote(function_name)(value)
        when is_integer(value) do
          %__MODULE__{value: trunc :math.fmod(value, unquote(max))}
        end
        def unquote(function_name)(a) do nil end

        unquote_splicing(Enum.map Enum.reduce(args, %{}, fn {k, v}, acc -> Map.put_new(acc, v, k) end), fn {value, variable} -> quote do
            def name(unquote(value)), do: unquote(variable)
          end
        end)
        def name(value) when is_integer(value) and value >= unquote(max) do
          name(trunc :math.fmod value, unquote(max))
        end
        def name(%__MODULE__{value: value}), do: name(value)


        defimpl String.Chars do
          def to_string(%@for{value: i}) when is_integer(i) do
            "%#{unquote(name_atom)}.#{@for.unquote(function_name)(i)}"
          end
        end

        defimpl Inspect do
          def inspect(%@for{value: v}, _opts) do
            "#{@for}.#{unquote(function_name)}(#{v})"
          end
        end

        unquote_splicing(elem(block, 2))
      end
    end
  end
end

(I broke up some of what I wrote above into a forward search pitch function and a backwards lookup name function. Doing them both in one function was just weird now that I decided to have a struct, which is the only way to get an implementation of to_string and inspect working).
used like so:


defmodule Music do
  require AEnum

  AEnum.defenum Pitch, [
      c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11,
      db: 1, eb: 3, gb: 6, ab: 8, bb: 10,
      "c#": 1, "d#": 3, "f#": 6, "g#": 8, "a#": 10,
      cb: 11, "b#": 0, fb: 4, "e#": 5
    ] do

    def note(%__MODULE__{value: value}, octave)
    when is_integer(octave) do
      note(value, octave)
    end

    def note(pitch, octave)
    when is_integer(pitch) and is_integer(octave) do
      12 * (octave + 1) + pitch
    end

    def sigil_P(s, [])
    when is_binary(s) do
      s |> String.split()
      |> Enum.map(&__MODULE__.pitch/1)
    end
  end
end

Then I can do

defmodule SomeModule
import Music.Pitch 

def major_scale do 
[:c, :d, :e, :f, :g, :a, :b] |> Enum.map(pitch)
end
end

I don’t understand this warning:

the String.Chars protocol has already been consolidated, an implementation for Music.Pitch has no effect. If you want to implement protocols after compilation or during tests, check the "Consolidation" section in the Protocol module documentation

I read the docs and it doesn’t explain how to make this warning change? Just explains that consolidation is used for improving lookup speed.

Let me know if this is sensible or downright strange