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
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
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
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
In Elixir this is referred to as an unquote fragment.
You may also find this post helpful.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










