wrachwal

wrachwal

Hi,

I am trying to define a macro with 3 arguments (name, var, body) which is to generate function name/1
a) with argument var, possibly arbitrary complex pattern-matching expression
b) besides var, other variables bound in the outer scope can be used in the body

Example:

defmodule Mac3Use do
  import Mac3
  for {g, v} <- [g1: 1, g2: 2] do 
    gen String.to_atom("#{g}_x"), a,           do: {:ok, 1, a}
    gen String.to_atom("#{g}_y"), %{y: y} = a, do: {:ok, y, a}
  # gen String.to_atom("#{g}_z"), a,           do: {:ok, v, a} # undefined function v/0
  end
end

Below the implementation satisfying a). With slight modification it can handle guards. Unfortunately it doesn’t handle b) - I get “undefined function v/0” CompileError.

defmodule Mac3 do
  defmacro gen(name, var \\ quote(do: _), do: block) do
    var = Macro.escape(var)
    block = Macro.escape(block)
    quote bind_quoted: [name: name, var: var, block: block] do
      def unquote(name)(unquote(var)) do
        unquote(block)
      end
    end
  end
end

I was trying to modify this code without luck. Inspect of binding() inside quote shows g and v variables from the example with proper values bound, but unquote(block) doesn’t want to consume v.

Just a moment ago I checked that

    @v  v
    gen String.to_atom("#{g}_z"), a, do: {:ok, @v, a}

works, but it’s rather a hack.

As you could notice, the macro resembles ExUnit.Case.test/3, and its implementation is simplified version of that from ExUnit.

Is there an easy solution to this?

Thank you,
Waldemar.

Showing Posts 1 to 10

OvermindDL1

OvermindDL1

The v in the :ok 3-tuple is scoped inside the gen macro, you need to unquote it:

gen String.to_atom("#{g}_z"), a,           do: {:ok, unquote(v), a}

And that ‘should’ work I think?

wrachwal

wrachwal OP

unquote(v) results in “unquote called outside quote” :frowning:

OvermindDL1

OvermindDL1

Ah it’s not working like it does in a def’s body, probably some special casing there… >.<

I wonder if it is because you are Macro.escapeing the block, why are you doing that (and why escaping the var too)?

wrachwal

wrachwal OP

Good question. Some sort of hacking. Sure, all macro arguments are already AST(s). Nevertheless without escaping e.g. var, it will choke on bind_quoted. Comment out escaping var and you’ll get “undefined function a/0” for the first function being generated that worked earlier.

During experiments I realized that bind_quoted is somehow opposite to escaping (hence the name). In this particular use case this second escape (first is implicit and forms macro arguments) seems to prevent “finding” a value in bind_quoted construct for what is to be a function argument - the variable, for which there is no value to bind, right?

All this hacking is not mine - see ExUnit.Case.test/3 for comparison.

https://github.com/elixir-lang/elixir/blob/v1.5.2/lib/ex_unit/lib/ex_unit/case.ex#L258-L280

OvermindDL1

OvermindDL1

Well the main thing I’m curious about is what is the point of this macro, wouldn’t this be easier?

  for {g, v} <- [g1: 1, g2: 2] do 
    def unquote(String.to_atom("#{g}_x"))(a),           do: {:ok, 1, a}
    def unquote(String.to_atom("#{g}_y"))(%{y: y} = a), do: {:ok, y, a}
    def unquote(String.to_atom("#{g}_z"))(a),           do: {:ok, unquote(Macro.escape(v)), a} 
  end

I think this is the XY problem, I’m not sure what is trying to be accomplished here. ^.^;

wrachwal

wrachwal OP

Let’s say I want compile time bindings (variables, not attributes) to be visible in the body of something like ExUnit.Case.test/3.

NobbZ

NobbZ

As you can see in the code, they are unquoteing contents (your body) first before escapeing it. Also they escape using the unquote: true option.

Perhaps you should evaluate those ways?

https://github.com/elixir-lang/elixir/blob/v1.5.2/lib/ex_unit/lib/ex_unit/case.ex#L258-L280

wrachwal

wrachwal OP

I tried this earlier with no desirable effect. I think the whole point behind this quote/unquote/escape is to assure that testcases return :ok (it’s mentioned in the comment above the macro’s head).

To unhide the context a bit - I’d want such a feature in macros I am building around common_test; In common_test you can put a set of testcases in one group, and to run such the set with different input configurations, you need to place that group into several intermediate groups and provide specific configuration for such intermediates.

Definining intermediate groups can be tedious, so looping with for/1 seems to be the best fit for the job. If I don’t find a way how to do that, the whole work won’t be lost - variable bindings from outer scope would be nice addition, though.

Nevertheless I think the problem seems interesting in general.

NobbZ

NobbZ

I still think unquote: true is the option you actually want… You’ll have to write unquote(v) then, but at least it should work. And as far as I remember ExUnit, you have to unquote “external” names there as well…

wrachwal

wrachwal OP

You are probably right… Quick check:

defmodule XXXTest do
  use ExUnit.Case
  ext = 1
  test "xxx", cfg do
    assert %{async: _} = cfg
    assert unquote(ext) == 2
  end
end

yields

  1) test xxx (XXXTest)
     test/xxx_test.exs:4
     Assertion with == failed
     code:  assert 1 == 2
     left:  1
     right: 2
     stacktrace:
       test/xxx_test.exs:6: (test)

I will play a little more tomorrow (now is close to midnight).
Thank you very much,
Waldemar.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews