laiboonh

laiboonh

Trying to define my own if macro

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 https://github.com/elixir-lang/elixir/blob/5feec03db6a134371d9c0f60cc8873232659005e/lib/elixir/lib/kernel.ex and the two points i mentioned seemed achievable. I don’t know what i am doing wrong. Any help is appreciated, thanks in advance.

Most Liked

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.

NobbZ

NobbZ

That never happened :wink:

Where Next?

Popular in Questions Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
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
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
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
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
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Other popular topics Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
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
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
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
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
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

We're in Beta

About us Mission Statement