roflbobl

roflbobl

Adding guard to macro that just calls another function

I have a problem, where i have a macro the calls another function. I want to be able to add optional guards to my macro, and call the nested function with those guards. I have a solution, but i feel like it is verbose and there is something i dont know of. Its the use of case do that feels clunky, and the only difference is the when clause.

Example

  defmacro assert_push(
             event,
             payload,
             timeout \\ 100
           ) do
    case payload do
      {:when, _, [pattern, guard]} ->
        quote do
          assert_receive %Phoenix.Socket.Message{
                           event: unquote(event),
                           payload: unquote(pattern)
                         }
                         when unquote(guard),
                         unquote(timeout)
        end

      _ ->
        quote do
          assert_receive %Phoenix.Socket.Message{
                           event: unquote(event),
                           payload: unquote(payload)
                         },
                         unquote(timeout)
        end
    end
  end

Most Liked

sodapopcan

sodapopcan

Honestly this looks fine to me—it’s nice and explicit. It’s not just the when clause that’s different but payload: unquote(pattern) v payload: unquote(payload). What you have is nice and explicit and I think DRYing it up would probably be more confusing (and hence a great case of “when DRY is too DRY”).

Although I see Chris K. is replying and he a notorious committer of macro crimes, so I’m interested to see what he’ll suggest :smiley:

sodapopcan

sodapopcan

He did not disappoint, but also made me more confident in my resolve :grin: TIL about :elixir_utils.extract_guards which is very cool. But for what it’s worth, in a tense debugging session (or even just coming across it randomly in while trying to understand a system) I would much rather see your code.

christhekeele

christhekeele

ezgif-2296daa4eccd89

christhekeele

christhekeele

Consider the (only semi-public but very stable) :elixir_utils.extract_guards/1 function from Elixir’s compiler. It takes in Macro AST of an expression, and returns a two-tuple of that expression with top-level guards removed, and a list of the guards extracted. Its implementation is similar to yours but handles multiple guards correctly (a rarely used feature in Elixir code, but syntactically valid).

Ex:

payload = quote(do: foo)
{pattern, guards} = :elixir_utils.extract_guards(payload)
#=> {{:foo, [], Elixir}, []}
payload = quote(do: foo when fizz)
{pattern, guards} = :elixir_utils.extract_guards(payload)
#=> {{:foo, [], Elixir}, [{:fizz, [], Elixir}]}
payload = quote(do: foo when fizz when buzz)
{pattern, guards} = :elixir_utils.extract_guards(payload)
#=> {{:foo, [], Elixir}, [{:fizz, [], Elixir}, {:buzz, [], Elixir}]}

You can then rebuild the guard AST around a new expression for insertion where desired without thinking about if there are zero or more by reducing over them:

new_guarded_expression = quote(do: bar)
Enum.reduce(guards, new_guarded_expression, fn guard, expression ->
  {:when, [], [expression, guard]}
end)
|> Macro.to_string
#=> "(bar when fizz) when buzz"
sodapopcan

sodapopcan

Yep, I’m glossing over a lot here, especially since OP’s code is clearly a test assertion. There is absolutely a huge difference in what’s acceptable in library v. application code and I was assuming this was for the latter (even if it is library-esque code within application code).

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

Other popular topics Top

aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
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
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New

We're in Beta

About us Mission Statement