defmacro __before_compile__(_env) do
quote do
Enum.each(@decoders, fn {type, decoder} ->
def decode(unquote(type), buffer) do
unquote(decoder).decode(buffer)
end
end)
end
end
Not sure if that’s your exact scenario but this works:
defmodule IntegerDecoder do
def decode(buffer) do
IO.puts("#{__MODULE__}: #{inspect(buffer)}")
end
end
defmodule BinaryDecoder do
def decode(buffer) do
IO.puts("#{__MODULE__}: #{inspect(buffer)}")
end
end
defmodule Injector do
@decoders [
{"integer", IntegerDecoder},
{"binary", BinaryDecoder}
]
defmacro __before_compile__(_env) do
quote bind_quoted: [decoders: @decoders] do
for {type, decoder} <- decoders do
def decode(unquote(type), buffer) do
unquote(decoder).decode(buffer)
end
end
end
end
end
defmodule Recipient do
@before_compile Injector
end
defmacro __before_compile__(_env) do
quote unquote: false do
for {type, decoder} <- @decoders do
def decode(unquote(type), buffer) do
unquote(decoder).decode(buffer)
end
end
end
end
Should do it. for comprehension is a macro, hence unquote: false is required to establish the right macro scope.
defmodule GenDecoder do
@decoders [
{"integer", IntegerDecoder},
{"binary", BinaryDecoder}
]
for {type, module} <- @decoders do
def decode(unquote(type), buffer), do: unquote(module).decode(buffer)
end
end
defmodule IntegerDecoder do
def decode(buffer), do: "int:#{buffer}"
end
defmodule BinaryDecoder do
def decode(buffer), do: "binary:#{buffer}"
end
Example of using the code:
iex(1)> GenDecoder.decode("integer", "a buffer")
"int:a buffer"
iex(2)> GenDecoder.decode("binary", "a buffer")
"binary:a buffer"
I thought about that too, but again struggled with implementation.
defmodule MyCoder do
use GenCoder
import_coder(IntegerDecoder)
import_coder(BinaryDecoder)
end
defmodule GenCoder do
defmacro import_decoder(module) do
quote unquote: false do
module = unquote(module)
type = module.get_type()
def decoder(unquote(type), buffer), do: unquote(module).decode(buffer)
end
end
end
(this doesn’t work )
why even have a macro?
If this is indeed a hot path…
Yeah, I was under the impression that pattern matching in Elixir/Erlang is extremely fast; much faster than map lookup. And people tend to want these binary encoders/decoders to be pretty quick.
For posterity (and people’s opinion), I’m writing an extendable BSON library. The goal is to easily be able to add new types beyond the official spec (for example, add a type for Date).
Protocols work great for encoding, but for decoding, need to have a user configurable dispatch table.
Something like this…
defimpl Beson.Encoder, for: Date do
@bson_type <<0x20>> # New type; not in spec
def encode(date) do
days = Date.to_gregorian_days(date)
{@bson_type, <<days::unsigned-little-32>>}
end
end
defmodule MyBeson do
use Beson
import_decoder(MyDateDecoder)
end
defmodule MyDateDecoder do
@bson_type <<0x20>> # New type; not in spec
def decode(buffer) do
<<days::unsigned-little-32, rest::binary>> = buffer
date = Date.from_gregorian_days(days)
{date, buffer}
end
end
MyBeson.encode!(%{today: Date.utc_today()})
|> MyBeson.decode!()
%{"today" => ~D[2021-05-17]}