Ripster
Function generation / dynamic dispatch
I am writing an application that reads incoming packets and I’d like to decode them and route them to the appropriate handler.
The header of each packet is what defines its type.
So for example a user login packet may look like this:
<<
header::integer-little-8,
ulen::unsigned-little-integer-32,
user::binary-size(ulen),
plen::unsigned-little-integer-32,
pass::binary-size(plen)
>>
I’d like to be able to do something like this in my receive function:
<<
header::integer-little-8,
data::bytes
>> = received
{:ok, packet} = Packet.parse(header, data)
:ok = packet.run()
Where Packet.parse/2 is defined at compile time.
defmodule Packet do
defmacro __using__(header) do
# track __CALLER__ and header?
@behaviour Packet
end
@callback parse(BitString) :: {:ok, Map}
@callback def run() :: :ok | {:error, reason}
# Generate these at compile time
def parse(1, data), do: Packets.Login.parse(data)
def parse(2, data), do: Packets.Chat.parse(data)
end
defmodule Packets.Login do
use Packet, header: 1
defstruct :username, :password
def parse(packet_data), do: # decode packet
def run, do: # run code to handle packet
end
Is there a way to do this using macros or a better way that wouldn’t require macros?
Marked As Solved
OvermindDL1
That says they are either on the wrong file or not put in early in the compilation process.
Honestly I’m thinking this is just a bit odd of a design overall regardless, I would probably just hardcode those all in as it’s not like they are going to be changing often anyway and adding new ones is easy. ^.^;
If you really want to go the more complex route (which be sure that you do), I’d really say take a look at my afore-mentioned protocol_ex, your solution might look similar to (typed in-post so likely has syntax errors):
# Define the main module:
import ProtocolEx
defprotocolEx Blah do
def parse(header, data) # Since no fallback body then this must be handled by the implementations
def parse(<<header::integer-little-8, data::bytes), do: parse(header, data)
end
Then just write the implementations like:
import ProtocolEx
defimplEx Login, 1, for: Packet do
def parse(_, data) do
# whatever you need here...
end
# You could add more `parse/2` callbacks here that handle other numbers as well if you want,
# or make other implementations like below
end
Then maybe another one somewhere else:
import ProtocolEx
defimplEx Chat, 2, for: Packet do
def parse(_, data) do
# whatever you need here...
end
end
Etc…
And of course you can add a run function and all sorts as well too. Whatever. (If you need new features in protocol_ex, just ask). It has a lot of other abilities and optimizations and such you can do as well, but as-is it already does what you posted that you want.
Don’t forget to add the compile for protocol_ex to the mix.exs file as per the documentation too (or you can always handle it manually too if you want).
(EDIT: Updated examples to be actual code rather than just placeholder code)
Also Liked
OvermindDL1
I really should release my library of mine that does that at compile-time… >.>
Hmm, actually you could use my ProtocolEx (it’s like the Protocol Library built into elixir, except it is based on matchers and guards instead of types, and has the ability to be faster too by inlining and more) library if you don’t mind putting your parse functions in implementation modules (you can of course put multiples of the parse functions in a single implementation if you want, so you can group like-functionality. ![]()
kip
I don’t think you need a macro for this. Something like this should do it. Of course you would probably
encapsulate the header->function mapping differently in real use rather than a module attribute.
defmodule Dynamic do
@function_map [
{1, Packets.Login},
{2, Packets.Chat}
]
def process_packet(<< header::integer-little-8, data::bytes >>) do
process_packet(header, data)
end
for {header, module} <- @function_map do
function_name = Module.concat(module, "parse")
def process_packet(unquote(header), data), do: unquote(function_name)(data)
end
end
mischov
My current favorite library to read for learning about neat code generation is NimbleParsec. It might give you some ideas. 
Edit: The Elixir Forum thread for the library is also good- searching it for “quoted expressions” might give you a few more ideas.
kip
Um, so how do you know at compile type what the packet types are, and what the processing should be for each packet? I’m surely misunderstanding you but it sounds like you’re saying something like this, which pattern matches on the header. These can easily be generated at compile time - but you’d still need to know what the processing-per-packet would be?
defmodule Dynamic do
def process_packet(<< 1::integer-little-8, data::bytes >>) do
:do_something_with_1
end
def process_packet(<< 2::integer-little-8, data::bytes >>) do
:do_something_with_2
end
end
Ripster
I’ll likely either hard code or use ProtocolEx as you’ve suggested. ProtocolEx does look really nice, the only reason I’ve been shying away from it is this is a learning experience for me.
Prior to this project I didn’t even know the difference between use, import, and require. As I’m reading and learning I’m trying to use everything I’m learning in some way even if it’s not the most appropriate for the situation. I need to get it to sink in somehow! 
I appreciate everyone’s patience and I’ve been really surprised by how helpful and knowledgeable the Elixir community is. Thank you for the examples, links, and information you’ve provided. It’s been a huge help.







