Hey guys - been hacking on Elixir projects for about two years now. I implemented a simple Instagram API wrapper library (exstagram) as well as an options pricing library. I’m working on an API wrapper client for Deribit, and because so many of the API endpoints start with “get” I figured it would be a good application of macros.
I learned a lot but had some trouble and wanted to ask some follow-up questions about what I could do better.
For reproduction/viewing my code, please see https://github.com/arthurcolle/Deribit.ex (feel free to git clone, etc).
Under lib/deribit/api/ you can see public.ex, which calls the macro - Macros.generate_endpoint() which accepts one atom as an argument (for now… adding query param/optional params support later). This has an atom passed in, which creates the function that is then callable by the user.
Question 1 - do I need to require and alias for importing a macro from a different module? If I remove the require, it complains that (despite specifying the macro explicitly with the full nested-macro path name Deribit.Helpers.Macros.generate_endpoint) it needs to require a macro. If I remove the alias, then I have to specify the entire path, which is obnoxious. Any words of advice here?
Question 2 - to get lib/deribit/helpers/macros.ex working, I needed to do some serious hackery:
defmodule Deribit.Helpers.Macros do
alias Deribit.Helpers.Utils, as: Utils
defmacro create_endpoint(endpoint, options \\ []) do
quote bind_quoted: [endpoint: endpoint] do
@endpoint Utils.scoping_workaround(endpoint)
def unquote(Utils.format(@endpoint, [slash?: false]))() do
get(Utils.format(@endpoint, [slash?: true]))
end
end
end
end
I needed to use module variables (@endpoint) to quickly save this bound variable [endpoint: endpoint] - whats a better way of doing the above?
The idea is that in a module, I’d say “CreateEndpoint :things” and I’d have a function getthings() that I wouldn’t need to define explicitly. I don’t like the fact I’m completely hacking module variables to quickly save a variable that is clearly in scope during the bind_quoted expression, but then somehow falls out of scope once I enter the unquote block - that I then use in the string-interpolated expression… its just not good and I recognize this but it was the fastest way to get the intended behavior.
I want to learn best practices here and appreciate any feedback.
To build -
git clone https://github.com/arthurcolle/Deribit.ex deribit
cd deribit && mix do deps.get, compile
iex -S mix
~|iex|> Deribit.API.Public.getinstruments()