voger

voger

Can someone please explain this 'my_if' code in the Metaprogramming Elixir book?

I just finished “Programming Elixir” and now I am reading “Metaprogramming Elixir”. I can’t say I comprehend everything. I try to understand but I mostly pretend I understand and keep reading. While the previous examples were somehow manageable to follow I am stuck in recreating the if macro. Here is the code.

defmodule ControlFlow do
  defmacro my_if(expr, do: if_block) do
     if(expr, do: if_block, else: nil)
  end

  defmacro 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

Can someone please be patient and explain with simple words what is happening in this code?

I understand what quote and unquote do. I don’t understand why the two defmacro definitions.

Now that I write the question I realize the first defmacro is just for the case we have a simple if without else. The second defmacro handles the if/else case. But still I can’t understand how they work.

Marked As Solved

chrismccord

chrismccord

Creator of Phoenix

Bah, yes it is indeed erratta. You’re the first to catch this in like 8 years! :slight_smile:

If you give in a non AST literal, it’s always true because we are passing the ast to if in the first clause, not the expanded expression. So for example, 1 > 2 will be the ast tuple, instead of the boolean, which is always truthy. The code should have quoted the first clause. As @LostKobrakai said tho, a better example wouldn’t proxy to Elixir’s if in the first place:

iex(2)>    
nil
iex(3)> defmodule ControlFlow do
...(3)>   defmacro my_if(expr, [{:do, if_block} | _] = opts) do
...(3)>     quote do
...(3)>       case unquote(expr) do
...(3)>       result when result in [false, nil] -> unquote(opts[:else])
...(3)>       _ -> unquote(if_block) end
...(3)>     end
...(3)>   end
...(3)> end
{:module, ControlFlow,
 <<70, 79, 82, 49, 0, 0, 6, 208, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 183,
   0, 0, 0, 20, 18, 69, 108, 105, 120, 105, 114, 46, 67, 111, 110, 116, 114, 
   111, 108, 70, 108, 111, 119, 8, 95, 95, 105, ...>>, {:my_if, 2}}
iex(4)> require ControlFlow
ControlFlow
iex(5)> ControlFlow.my_if 1 > 2, do: :here
nil
iex(6)> ControlFlow.my_if 3 > 2, do: :here
:here
iex(7)> 

Also Liked

brainbag

brainbag

If you understand that code like this:

if something do
  x
else 
  y
end

is simply syntactic sugar for this:

if something, do: x, else: y

and that is simply syntactic sugar for this:

if(something, [do: x, else: y])

and THAT is simply syntactic sugar for this:

if(something, [{:do, x}, {:else, y}])

then it might be a bit clearer. Remember that Elixir allows you to drop the List [] if you’re passing in a keyword list.

Some more detail: your first my_if function takes two arguments, one of which is an explicit keyword list of do:. Since else isn’t specified, it just passes nil in for the else. That will run when you call code like this:

my_if x == 2 do
  x
end

# or
my_if(x == 2, do: x)

# which, without the sugar, is actually this:
my_if(x == 2, [{:do, x}])

Your second my_if function does a case statement on the expression (something in my example). Inside of the case statement, it checks to see if the result evaluation is one of false or nil, and then returns the else_block. Otherwise, it returns the if_block.

my_if x == 2 do
  x
else
  y
end

# or 
my_if(x == 2, do: x, else: y)

# which, without the sugar, is actually this:
my_if(x == 2, [{:do, x}, {:else, y}])

# and this is, conceptually, what your my_if function does
case x == 2 do 
  false -> y
  nil -> y
  _ -> x
end

You might check the “Keywords and maps” section of the documentation for further reference: http://elixir-lang.org/getting-started/keywords-and-maps.html

Hope that helps!

brainbag

brainbag

No, because you forgot something very important in your first definition. :slight_smile:

If you do an IO.inspect in that first my_if, what is the value of expr? It’s not what you think!

When you figure that out, think about how you “transform” values so they’re usable by macros, and then look at your first my_if again and see what you’re missing.

kodepett

kodepett

Hi, kindly find below - elixir v1.13.3

iex(92)> ControlFlow.my_if false, do: :here  
nil
iex(93)> ControlFlow.my_if true, do: :here 
:here
iex(94)> ControlFlow.my_if 1 > 2, do: :here
:here
iex(95)> ControlFlow.my_if 1 == 2, do: :here
:here

Where Next?

Popular in Questions Top

siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
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
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
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
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
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
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New

We're in Beta

About us Mission Statement