Hi all, I come from a relatively brief Erlang background from many years ago, and I’m trying now to think (again) in that distributed way - while learning Elixir, which I prefer at a glance.
In short, I’d like to know what (if any) is the Elixir-way to deal with a pattern I encounter often my side projects: the command pattern.
Assume I have a little TCP server setup (via ranch, in fact) and I want to have a clean way of adding commands to manipulate “global state” (currently, I’m using Mnesia… but I already feel like this is not well supported in Elixir. A question for another day).
I went the polymorphism way, so each command is a module with behaviour Command
which, in itself, defines callbacks. A series of processes run these commands.
However, clearly each string coming from the socket needs to be processed and validated as a Command. This is my attempt:
defp parse!(_, ["quit" | _]) do {:ok, :quit} end
defp parse!(_, ["shutdown" | _]) do {:ok, :shutdown} end
defp parse!(_, ["echo" | opts]) do {:ok, {:echo, Enum.join(opts, " ")}} end
defp parse!(_, cmd) when cmd == [] do {:ok, {:echo, ""}} end
defp parse!(_state, [cmd | opts]) do
module_name = Macro.camelize(cmd)
try do
module = String.to_existing_atom("Elixir.Commands.#{module_name}")
{:ok, {module, opts}}
rescue
_ -> {:ok, {:echo, "#{cmd}: UNKNOWN_COMMAND"}}
end
end
It works, but I think it’s very brittle. So I’m trying meta-magic but I’m not sure I’m going in the right/sensible/idiomatic direction:
defmacro __using__(_opts) do
quote do
@behaviour Command
@on_load :register_command
def register_command() do
Command.register_command(__MODULE__)
end
end
end
This essentially allows me to register a command implementation, when it’s loaded. Again, it works, but I’m not sure it’s the right way.
If you can advise or point me in the right direction, I’d be very grateful Elixir is a very interesting language and I’d love to continue working with it.