I'm trying to get a better understanding of Elixir macros, but I'm having trouble figuring out how to set up a scenario where I can pattern match the argument that I pass to a macro when the value I'm trying to pass is a variable. Here's a simple example to illustrate:
macro_test.ex
defmodule MacroTest do
use MacroTest.UseMe
def run() do
atom = :hello_world
macro_a(atom)
end
end
use_me.ex
defmodule MacroTest.UseMe do
defmacro __using__(_) do
quote do
defmacro macro_a(:hello_world) do
quote do
"Success!"
end
end
defmacro macro_a(:some_other_atom) do
quote do
"Did something else..."
end
end
end
end
end
When I try to compile this code, I get the error
== Compilation error in file lib/macro_test.ex ==
** (FunctionClauseError) no function clause matching in MacroTest.macro_a/1
If I change the initial run() function so that the atom is passed directly to the macro, such as macro_a(:hello_world), then it compiles/runs just fine.
How can I alter this scenario so that the macro can pattern match on a value that has been provided as a variable, rather than the literal value itself?
It depends on what you want to do. Macros run at compile time, and operate on portions of source code (as opposed to the run time value returned by said piece of source code). So in this case, the macro knows that it has been called with a variable called atom, but it has no way of knowing that this variable has been assigned to earlier in the calling function.
You could check the value of the given variable at run time:
defmacro macro_a(a) do
quote do
case unquote(a) do
:hello_world ->
"Success!"
:some_other_atom ->
"Did something else..."
end
end
end
That is, every invocation of macro_a gets replaced by the case expression above, using whatever variable or other expression was passed as argument.
Related
I'll start with my code:
macro example(args...)
local s = __source__
println(s) # This part works, showing macro is called on line 9
quote
println(s) # Julia tells me this variable "s" is not defined
println(__source__) # Likewise, "__source__" is not defined here either
end
end
#example 42 # Line 9 of my file
In my macro above I want to record the line number that is calling the macro and use it within my quote block. Both capturing it in a variable outside the quote block and using it within, or using it directly in the quote block don't work. My understanding is the code outside the quote block runs at parse-time, and the expression returned from the quote block is evaluated at run-time.
I feel like there must be a way to capture that variable and inject it right into the expression that will be evaluated later, but I haven't figured out how to do that. Any help here is appreciated. If there is a better way to do this let me know.
I ended up finding out an answer on my own. In the second line if I changed __source__ to __source__.line or __source__.file it worked fine as long as I then used $ to interpolate the result into the expression the macro returned. I'm still not sure why __source__ on its own didn't work, but using either .line or .file methods is working for me now.
I'm experiencing a similar problem trying to use __source__.
I think I can offer insight into why source.line, etc worked though.
The value of source.line is an integer. The value of source.fike is a string. Numbers and strings evaluate to themselves.
A symbol, on the other hand, evaluates to whatever value it has in the environment.
I would like to define a macro such that I can pass a do block into it, and have the macro create a function that calls the block, with an argument. I am running into a chicken & egg issue because the code below complains that name is not defined.
defmodule MyMacro do
defmacro greet(do: block) do
quote do
def hello(name), do: unquote(block)
end
end
end
defmodule Test do
import MyMacro
greet do
IO.puts("Hello, #{name}!")
end
end
Attempting to compile this code results in:
(CompileError) iex:6: undefined function name/0
(stdlib) lists.erl:1338: :lists.foreach/2
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(iex) lib/iex/evaluator.ex:250: IEx.Evaluator.handle_eval/5
From my understanding, this blows up even before it gets to my macro because elixir attempts to generate an AST for the do block before calling my macro, but name is undefined.
My goal is to be able to call Test.hello("world") after compiling the DSL. Is that possible in Elixir?
Elixir's macros are hygienic so if you declare a variable in a macro's quote, it won't be available to the caller. You can disable this by wrapping the declaration of the variable with var!:
quote do
def hello(var!(name)), do: unquote(block)
end
I'm trying to invoke a private macro, within a quote block, using a variable defined within the code block itself.
This is the pseudo-code showing what I would like to do (doesn't work)
defmodule Foo do
defmacrop debug(msg) do
quote bind_quoted: [msg: msg], do: IO.puts(msg)
end
defmacro __using__(_) do
quote do
def hello do
my = "testme"
unquote(debug(quote do: my))
end
end
end
end
defmodule Bar do
use Foo
end
Bar.hello()
And this would get converted (in my mind), at compile time to:
defmodule Bar do
def hello do
my = "testme"
IO.puts(my)
end
end
Is there any way to achieve this? I'm struggling to find any documentation related to it.
Update
I discovered that:
defmodule Foo do
defmacrop debug() do
quote do: IO.puts("hello")
end
defmacro __using__(_) do
quote do
def hello do
my = "testme"
unquote(debug())
end
end
end
end
Gets properly converted to what I need, but I'm struggling find a way to pass the variable as is, so that it becomes IO.puts(my)
The issue here is with nested quoting: the private macro should return the double-quoted expression (since to invoke it from the outer scope one needs to explicitly unquote, and macro is still expected to return a quoted expression.)
Sidenote: your update section is wrong; you might notice, that "hello" is printed during a compilation stage, namely when use Foo is being compiled. That is because the double-quoting is needed, the code in your update section executes IO.puts when unquote in __using__ macro is met.
On the other hand, my should be quoted only once. That might be achieved with an explicit quoting of AST, passing the msg there as is:
defmodule Foo do
defmacrop debug(msg) do
quote bind_quoted: [msg: msg] do
{
{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]},
[],
[msg]} # ⇐ HERE `msg` is the untouched argument
end
end
defmacro __using__(_) do
quote do
def hello do
my = "testme"
unquote(debug(quote do: my))
end
end
end
end
defmodule Bar do
use Foo
end
Bar.hello()
#⇒ "testme"
I was unable to achieve the same functionality with options in the call to Kernel.SpecialForms.quote/2; the only available related option is unquote to tune the unquoting inside nested quotes, while we need the exact opposite.
Sidenote: below does not work and I expect this to be a bug in Kernel.SpecialForms.quote/2 implementation.
quote bind_quoted: [msg: msg] do
quote bind_quoted: [msg: msg], do: IO.puts(msg)
end
FWIW: I filed an issue.
I believe it might be a good feature request to Elixir core, to allow an option that disables additional quoting.
Sidenote 2: the following works (most concise approach):
defmacrop debug(msg) do
quote bind_quoted: [msg: msg] do
quote do: IO.puts(unquote msg)
end
end
So you might avoid tackling with an explicit AST and just use the above. I am leaving the answer as is, since dealing with AST directly is also a very good option, that should be used as a sledgehammer / last resort, which does always work.
If IO.puts is not your desired target, you might call quote do: YOUR_EXPR on what you want to have in debug macro:
quote do: to_string(arg)
#⇒ {:to_string, [context: Elixir, import: Kernel], [{:arg, [], Elixir}]}
and manually unquote the arg in the result:
# ✗ ⇓⇓⇓ {:arg, [], Elixir}
# ✓ ⇓⇓⇓ arg
{:to_string, [context: Elixir, import: Kernel], [arg]}
This is basically how I got the AST of your original request (IO.puts.)
I have a Julia function that takes a few input arguments and uses them to perform multiple operations. In order to get one of those operations to work properly I need to be able to compute a symbol that matches the user input. For example the function looks something like this:
function func(arg1,arg2)
symb_arg1 = ## get the symbol for input into arg1
symb_arg2 = ## get the symbol for input into arg2
println(symb_arg1)
println(symb_arg2)
## Do some operations using arg1, arg2, symb_arg1, symb_arg1
end
I am hoping to achieve the following behavior:
a = 25
b = rand(27,55,18)
func(a,b) ## prints :a and :b
The difficulty here is to get the function to compute a symbol containing the actual name of the variable, rather than the value of the variable. This post provides the following macro that almost does what I want:
macro mymacro(ex)
Expr(:quote,ex) # this creates an expression that looks like :(:(x + 2))
end
This macro works well for doing the following:
a = rand(27,15)
symb_a = #mymacro(a) ## prints :a
However, using this macro inside my function will not produce the desired effect. Specifically, if I define my function as:
function func_bad(arg1,arg2)
symb_arg1 = #mymacro(arg1)
symb_arg2 = #mymacto(arg2)
println(symb_arg1)
println(symb_arg2)
## Do some operations using arg1, arg2, symb_arg1, symb_arg1
end
And then run the commands:
a = 25
b = rand(27,55,18)
func_bad(a,b) ## prints :arg1 and :arg2 (instead of the desired :a and :b)
Of course, one simple (but not so elegant) solution is to add additional input arguments for the function so that the user is responsible for creating the symbols. However this is more of a last resort. I would prefer the function be able to automatically create the symbols. Any idea as to how I can modify my function or the macro to achieve this behavior?
The simple answer is that this is not possible; there is an abstraction barrier that prevents functions from seeing implementation details of their callers. All they get is the values, which is a crucial property for robust and clear programs.
One thing you could do is write a macro to transform the call site:
#with_syms func(a, b)
into something like
func((:a,a), (:b,b))
passing symbol-value pairs into the function.
Another slightly different design would be to provide macro wrappers for all functions that need this behavior, so that calls would look like #func(a,b). You could factor out the argument list transformation to a helper function; each macro would look like
macro func(args...)
:(func($(pair_with_symbols(args)...)))
end
I am attempting to port a macro from MASM6 to TASM5 (in IDEAL mode) and I am encountering errors. The macro itself assembles fine, but when I attempt to call it, I receive the following error during assembly:
Error xxx.asm(##) Can't use macro name in expression: M_SWAP16
The macro takes the numeric value from a text macro and performs a byte swap. The macro is generally called with ops that take immediate values or during variable initialization.
MACRO M_swap16 operand
LOCAL result
result = (((operand and 0FFh) shl 8) or ((operand and 0FF00h) shr 8))
exitm %result
ENDM
IPPROTO_TCP EQU 6
.
.
.
mov [protocol], M_swap16(IPPROTO_TCP) ; fails
.
.
.
protocol DW ?
protocol_default DW M_swap16(IPPROTO_TCP) ; fails
It works fine in MASM 6.11. Switching TASM from IDEAL to MASM mode doesn't help. Neither does moving the macro into the EQU statement. Ideas?
Unfortunately TASM5 doesn't appear to support macros returning results to expressions at least according to the last official docs. This is also what the error you are seeing is saying. More specifically, the EXITM directive doesn't take an argument like MASM can regardless of the mode you are in. However TASM's macros can still emit a line of code, so if you aren't worried about passing the expression in to the macro, I propose the following workaround (IDEAL mode):
MACRO M_swap16_EXPRESSION expr,operand
LOCAL result
result = (((operand and 0FFh) shl 8) or ((operand and 0FF00h) shr 8))
expr result
ENDM
The macro above takes an additional argument "expr" as the 1st argument which is the assembly expression you were trying to plug the original expression in. It will perform the assembly-time arithmetic on the operand and emit the final assembly line. It can be used like this:
M_swap16_EXPRESSION <mov [protocol],>,IPPROTO_TCP
...
M_swap16_EXPRESSION <protocol_default DW>,IPPROTO_TCP
I admit its ugly, but it might be the next best thing if you must use TASM.