vshesh

vshesh

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

Hi everyone,

trying to encode some information about music (a different structure than the harmonex package).
I have an earlier implemenation of what I want in python (from a previous project). It heavily uses enums to describe the structure of pitch classes, intervals, chords etc.

eg one of them:

"""
Simple mapping of note name to MIDI value mod 12.
MIDI 0th octave begins at 12 + this value. (so C0 = 12, A0 = 21, etc)
"""
import aenum
Pitch : 'Pitch' = IntEnum('Pitch', {
  # note, this order is NOT random
  # raw note names are preferred to enharmonic equivalents
  # flats are preferrable to sharps (jazz musician here)
  'C': 0,  'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11,
  'Db':1,  'Gb':6, 'Ab':8, 'Bb':10, 'Eb': 3,
  'C#':1, 'D#':3, 'G#':8, 'A#':10,  'F#': 6,
  # these are just weird, so they exist only as aliases for B/C E/F respectively.
  'Fb':4,'E#':5,'B#':0, 'Cb':11
})

Is there something equivalent in erlang/elixir that allows me to have similar properties as an enum like this? “Just use a map” only allows me to do lookups in one direction - i also want to be able to do them in the other direction. So would want to be able to do:

> Pitch.C
Pitch.C (as opposed to 0 as the representation)
> Pitch("A#") => Pitch.Aꖛ #found a unicode character that elixir accepts that looks like a sharp
> Pitch(:Cꖛ) => Pitch.Cꖛ 
> Pitch(12) => Pitch.C #appropriate instance, mod 12
> Pitch(10) => Pitch.Bb # chooses the first instance when there are more than one option
> Pitch.C => Directly choose the right instance
> Pitch.C = Pitch.Cꖛ => :false
> import Pitch; import Interval
> C + m2 => Pitch.Cꖛ 
> major_scale = [U, M2, M3, P4, P5, M6, M7] # these are Interval.U, Interval.M2 etc
> minor_11 = ~I(m3 P5 m7 M9 P11) => [m3, P5, m7, m9, P11] # again, Interval.m3, Interval.P5 etc

The shorthand import structure are very useful to not have to type out Pitch._ and Interval._ every time.

Is this what @ is for?

defmodule Pitch
@C = 0
@Cs= 1
@Db= 1
@D = 2
...
end

?

Marked As Solved

vshesh

vshesh

Yeah I learned that ElixirLS is just complaining and things work fine when I run with iex -S mix

I’m pretty happy with this final solution for now, so leaving it here in case it helps others:

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)
    quote do
      defmodule unquote(fullname) do
        defstruct value: 0
        @type t :: %__MODULE__{value: integer}
        
        def unquote(function_name)(%__MODULE__{value: v} = instance) do
          instance
        end
        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)

        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)
            def unquote(function_name)(unquote(value)) do %__MODULE__{value: unquote(value)} end
          end
        end)
        def name(%__MODULE__{value: value}), do: name(value)


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

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

        unquote(block)

        # catch all clauses go at the end so that other implementation can
        # add more clauses if they wish for other reasons.
        def unquote(function_name)(a), do: nil
        def name(a), do: nil
      end
    end
  end

Also Liked

kip

kip

ex_cldr Core Team

Since the order is important, an Elixir/erlang map is not the right structure since order is not defined

If the list is known at compile time (ie its static) then you can compile your mappings into functions. For example:

defmodule Pitch do
  @pitches [{"C", 0}, {"D", 2}, ...]

  for {note, midi_value} <- @pitches do
    def pitch(unquote(note)), do: unquote(midi_value)
    def pitch(unquote(midi_value)), do: unquote(note)
  end

end

Since functions clauses are resolved in lexical order, this implementation does rely on the keys and the values being unique.

kip

kip

ex_cldr Core Team

Yes, you are right and I’m definitely not sure my approach is the best one but I like it for its expressiveness and simplicity.

kip

kip

ex_cldr Core Team

In Elixir this is referred to as an unquote fragment.

You may also find this post helpful.

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
vac
Hi, I’m quite new in Elixir and I’m trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and I...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New

Other popular topics Top

hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
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
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127089 1222
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement