laiboonh

laiboonh

defmodule Control do
  defmacro my_if(expr, do: if_block, else: else_block) do
    quote do
      Control.do_my_if(unquote(expr), do: unquote(if_block), else: unquote(else_block))
    end
  end

  defmacro my_if(expr, do: if_block) do
    quote do
      Control.do_my_if(unquote(expr), do: unquote(if_block), else: nil)
    end
  end

  def do_my_if(expr, do: if_block, else: else_block) do
    case (expr) do
        result when result in [false, nil] -> else_block
        _ -> if_block
    end
  end
end
iex(1)> c "control.exs"
[Control]
iex(2)> require Control
Control
iex(3)> Control.my_if(2==2, do: "true", else: "false")
"true"

What i don’t like about my implementation is that i would like to keep do_my_if as private function but this doesn’t compile if i do that.
I also don’t like the fact that i have to call Control.do_my_if in the macro body, why can’t i simply do do_my_if. During the AST expansion phase do_my_if should be totally legal shouldn’t it?

I looked at the Elixir source code elixir/lib/elixir/lib/kernel.ex at 5feec03db6a134371d9c0f60cc8873232659005e · elixir-lang/elixir · GitHub and the two points i mentioned seemed achievable. I don’t know what i am doing wrong. Any help is appreciated, thanks in advance.

Showing Posts 1 to 6

OvermindDL1

OvermindDL1

Your macro’s are calling do_my_if in the ‘callers’ scope, not the macro scope. This is proven by showing that both branches are taken:

iex> import Control
Control
iex> my_if(2==2, do: IO.inspect(true), else: IO.inspect(false))
true
false
true

You can change it to (and should, you cannot make it private because anything a defmacro calls (actually calls, not inside a quote) needs to be publicly accessible, I’m not sure why but they do) by changing your module to be:

defmodule Control do
  defmacro my_if(expr, do: if_block, else: else_block) do
    Control.do_my_if(expr, do: if_block, else: else_block)
  end

  defmacro my_if(expr, do: if_block) do
    Control.do_my_if(expr, do: if_block, else: nil)
  end

  def do_my_if(expr, do: if_block, else: else_block) do
    quote do
      case unquote(expr) do
          result when result in [false, nil] -> unquote(else_block)
          _ -> unquote(if_block)
      end
    end
  end
end

Which then results in:

iex> import Control
Control
iex> my_if(2==2, do: IO.inspect(true), else: IO.inspect(false))
true
true

:slight_smile:

I can detail ‘why’ if curious, but looking over the code and getting it to ‘click’ yourself can often be more enlightening in the tough world of macros. :slight_smile:

laiboonh

laiboonh OP

Wow i have much to learn, am i right to say that unquote evaluates the expression it is “unquoting” and inserts the result in the expanded AST. Hence my original implementation leads to

Control.do_my_if(true, do: true, else: false)

when running your example usage

NobbZ

NobbZ

unquote does not execute anything.

But, lets try to expanding your macro by hand. Perhaps we can enlighten you that way!

Your intitial version:

defmodule Control do
  defmacro my_if(expr, do: if_block, else: else_block) do
    quote do
      Control.do_my_if(unquote(expr), do: unquote(if_block), else: unquote(else_block))
    end
  end

  defmacro my_if(expr, do: if_block) do
    quote do
      Control.do_my_if(unquote(expr), do: unquote(if_block), else: nil)
    end
  end

  def do_my_if(expr, do: if_block, else: else_block) do
    case (expr) do
        result when result in [false, nil] -> else_block
        _ -> if_block
    end
  end
end

In the wollowing steps, I wont use the AST, but the human readable representation of the AST to simplify things a bit.

And now lets expand Controlö.my_if(false, do: IO.inpsect true, then: IO.inspect false:

First lets fill in the gaps of the Macro:

quote do
  Control.do_my_if(unquote(false, do: unquote(IO.inspect true), then: unquote(IO.inspect false)))
end

Now lets do the unquote:

Control.do_my_if(false, do: IO.inspect true, then: IO.inspect false)

As you can see, we have finished expanding and compiling the macro. We have left a plain function call in the BEAM-byte code. Its arguments will be evaluated at runtime.

Now lets try the version of @OvermindDL1 which I won’t paste again, using the same snippet as above:

The first thing to mention is, that there is no quote in the macro definition, therefore the function is called at compile time! In that function is a quote, therefore wi will look at that now:

quote do
  case unquote(false) do
      result when result in [false, nil] -> unquote(IO.inspect false)
      _ -> unquote(IO.inspect true)
  end
end

And now unquote this:

case false do
    result when result in [false, nil] -> IO.inspect false
    _ -> IO.inspect true
end

As you can see, this version expands into a case-expression and not a function call. Also since do_my_if is called from inside Control during compiletime (not from another module during runtime as in your version), you should be able to even defp it in Overminds version.


Somewhere burried in the Macro module, there was a function or macro which was able to print the expanded sourcecode of a macro-call. That is very nice when debugging them.

OvermindDL1

OvermindDL1

Yep, NobbZ said it wonderfully!

Macro.expand_once (or Macro.expand to expand all steps) along with I think it was Macro.to_string or something like that. :slight_smile:

s/unquote// :slight_smile:

NobbZ

NobbZ

That never happened :wink:

laiboonh

laiboonh OP

Thanks guys for helping my out with this. It’s really helpful to know how to expand on the macros by hand. As an extension to this “lesson”, may i verify my understanding using a streamlined version example below:

It will be very very helpful if you can go through again by hand how this whole chain of a more streamlined example got expanded or evaluated. I understand it to be that during “AST expansion phase”, the body of call_go_macro will be expanded into do_go(1) which ultimately expands to the AST of IO.inspect unquote(1). Then during the final “compilation to bytecode” phase that AST is compile to bytecode and whenever call_go_macro is called upon, IO.inspect(1)'s bytecode representation is executed during runtime

defmodule Test do
  defmacro go do
    do_go(1)
  end

  def do_go(var) do
    quote do
      IO.inspect unquote(var) 
    end
  end

  def call_go_macro do
    go()
  end
end
— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
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
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
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
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
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
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
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews