vrod

vrod

First time Elixir macro -- generate similar functions?

This is my first time I am looking at macros. I think I understand they are something like “pre generation” of code. Maybe something like using PHP templates to make Javascript code – they make Elixir code before it runs? OK this is maybe a bad example.

What I am trying to do is this: I have multiple functions that are almost all the same. Each one gets a thing from an API. Each one is the same, but each one returns a different struct. All of them look like this:

defmodule Thing1Resolver do
   def get_one(args) do
     data = get_from_api(args)
     struct(Thing1, args)
   end
end

The only difference is the module Thing1 is different for each function (Thing2, Thing3, Thing4). Can I write a macro to use and build functions like this? So maybe my resolver modules look like

defmodule Thing1Resolver do
    use Shared, struct: Thing1
end

Is this possible? Maybe it is a bad idea. I want to understand how it works and then I can decide.

Thankyou

Most Liked

al2o3cr

al2o3cr

You could use macros for this, but since it’s really just substituting a value for thing it’s not a great fit.

Macros are really powerful when you want a piece of code to generate multiple related functions / data, or code that depends on the whole module’s settings. For instance, the Ecto.Schema.schema macro generates __changeset__ and __schema__ functions after all the schema fields are defined along with setting many module attributes.

al2o3cr

al2o3cr

Because this:

defmodule FooBar.Thing1Resolver do
  use FooBar.Share, struct: FooBar.Thing1
  import FooBar.Creator

  defcreate(FooBar.Thing1)
end

is harder to read than this:

defmodule FooBar.Thing1Resolver do
   def new(args) do
    struct(FooBar.Thing1), args)
  end

  def create(args) do
    struct(FooBar.Thing1), args)
  end
end

For instance, it’s considerably less obvious the functions generated in Thing1Resolver in the first version are identical since their definitions are split among two modules and use arguments differently.

Another approach to consider: put as little code in the macro as possible and call out to the real implementation. This is part of what happens when you say use Ecto.Repo in your application:

defmodule FooBar.Share do
  defmacro __using__(opts) do
    struct_name = opts[:struct]

    quote do
      def new(args) do
        FooBar.Share.new(unquote(struct_name), args)
      end
    end
  end

  def new(struct_name, args) do
    struct(struct_name, args)
  end
end

defmodule Wat do
  use FooBar.Share, struct: Wat

  defstruct [:val]
end

Wat.new(val: 1)

Here the macro generates a function which adds in the argument passed to use but delegates all the functionality to a function defined on FooBar.Share. There’s a tiny compilation space/performance improvement since less AST is generated, and there’s less thinking about tricky things with unquote.

John-Goff

John-Goff

Why not just have a function that takes the type of struct? Something like

def get_generic(thing, args) do
  data = get_from_api(args)
  struct(thing, args)
end

Also note with this code you’re not using the API result for anything, and it’s essentially doing nothing, assuming there are no side effects from that API call.

Last Post!

vrod

vrod

I see what you are saying, I am only looking for a simple example – showing how to generate 1 function is better example for teaching than the 2 modules (I confess I did not understand this). There is big differences in what is good example for teaching and how to use the techniques in real code but I understand your concern. Thank you!

Where Next?

Popular in Questions Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New

Other popular topics Top

openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement