rcm765

rcm765

With the following code:

defmodule Foobar do
  defmacro b(arg) do
    arg
    |> String.split("\n")
    |> IO.inspect()
  end

  defmacro a() do
    other(&b/1)
  end

  def other(thing), do: thing
end

during compilation of this module this error occurs:

** (FunctionClauseError) no function clause matching in String.split/3    
    
    The following arguments were given to String.split/3:
    
        # 1
        {:x1, [], :elixir_fn}
    
        # 2
        "\n"
    
        # 3
        []
    
    Attempted function clauses (showing 4 out of 4):
    
        def split(string, %Regex{} = pattern, options) when is_binary(string) and is_list(options)
        def split(string, "", options) when is_binary(string) and is_list(options)
        def split(string, [], options) when is_binary(string) and is_list(options)
        def split(string, pattern, options) when is_binary(string) and is_list(options)
    
    (elixir 1.15.5) lib/string.ex:478: String.split/3
    (stdlib 5.0.2) erl_eval.erl:750: :erl_eval.do_apply/7
    (stdlib 5.0.2) erl_eval.erl:1026: :erl_eval.expr_list/7
    (stdlib 5.0.2) erl_eval.erl:456: :erl_eval.expr/6
    expanding macro: Foobar.b/1
    #cell:opffgr7vdttksyult6zxtm6vlvo6paax:1: (file)

This implies that the code is being executed, but how and why?

Showing Posts 1 to 7

sodapopcan

sodapopcan

Isn’t it because you aren’t quoteing it?

kip

kip

ex_cldr Core Team

The first rule of macros is that the accept AST and they must return AST. The way this is typically accomplished is to wrap the code you want executed at runtime in a quote expression. For example:

defmacro b(arg) do
  quote do
    unquote(arg)
    |> String.split("\n")
    |> IO.inspect()
  end
end

Note that in order to apply the arg inside the quoted expression, it needs to be unquoted.

rcm765

rcm765 OP

@kip, that’s not the issue here. The macro this was derived from is being passed in an AST. I’m just demonstrating how the code is be executed despite it not being called anywhere.

kip

kip

ex_cldr Core Team

Hmmm, other than disambiguating the reference to &b/1 I just copied and pasted into iex and didn’t see the same output (Elixir 1.15.5, OTP 26)

iex(1)> defmodule Foobar do
...(1)>   defmacro b(arg) do
...(1)>     arg
...(1)>     |> String.split("\n")
...(1)>     |> IO.inspect()
...(1)>   end
...(1)>
...(1)>   defmacro a() do
...(1)>     other(&Foobar.b/1)
...(1)>   end
...(1)>
...(1)>   def other(thing), do: thing
...(1)> end
warning: you must require Foobar before invoking the macro Foobar.b/1
  iex:9: Foobar.a/0

{:module, Foobar,
 <<70, 79, 82, 49, 0, 0, 7, 8, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 224, 0,
   0, 0, 23, 13, 69, 108, 105, 120, 105, 114, 46, 70, 111, 111, 98, 97, 114, 8,
   95, 95, 105, 110, 102, 111, 95, 95, ...>>, {:other, 1}}

I mistakenly assumed the snippet posted wasn’t the full example, sorry about that.

rcm765

rcm765 OP

Yes I’ve confirmed the same on my end. Not sure if this should be considered a bug or not.

hauleth

hauleth

It is called - there other(&b/1). Macros aren’t (at least directly) available in runtime, so cannot be referenced. So what Elixir does is that it treat it the same way as fn a -> b(a) end, which mean it need to expand b(a) call. And there is where your code is evaluated.

zachallaun

zachallaun

There are at least two things going wrong here:

  1. @hauleth is absolutely right, the &b/1 is being rewritten to fn x1 -> b(x1) end. The macro b then expands with the argument x1, which is {:x1, [], :elixir_fn} as AST. This is why you see the {:x1, [], :elixir_fn} being passed to String.split/3 in your example.

  2. Even if you rewrite the body of a to handle a non-string argument, e.g. ignore the argument completely and just return :ok, returning the anonymous function fn x1 -> b(x1) end from b is invalid:

    ** (CompileError) scratch.exs: invalid quoted expression: #Function<0.1526323/1 in Foobar."MACRO-a"/1>
    

    To get things to compile, a needs to be rewritten to return valid AST:

      defmacro a do
        quote do
          Foobar.other(&Foobar.b/1)
        end
      end
    

I suspect, however, that this might not address the actual code that this example was based off of. What I think you’re trying to do is to defer macro expansion until runtime. This isn’t possible except by evaluating a quoted expression, but that is a whole other can of worms. If this is the case, it’s worth reconsidering the approach.

— All posts loaded —

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
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
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
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

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
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
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
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
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews