Using guards in Elixir macros - macros

I am working on macro which would take a function and add some additional functionality. Eg.:
This:
defstate this_works(a, b) do
a + b + 1
end
Should be converted to this:
def this_works(a, b) do
IO.puts("LOGGING whatever")
a + b + 1
end
This is what I have so far. Try running this piece of code in iex:
defmodule MyMacro do
defmacro defstate(ast, do: block) do
{fn_atom, _} = Macro.decompose_call(ast)
quote do
def unquote(fn_atom)(var!(a), var!(b)) do
IO.puts("LOGGING")
unquote(block)
end
end
end
end
defmodule Test1 do
import MyMacro
defstate this_works(a, b) do
a + b + 1
end
end
Test.this_works(1, 2)
This works as expected.
Now, this module does not compile:
defmodule Test2 do
import MyMacro
defstate this_fails(a, b)
when 1 < 2
when 2 < 3
when 3 < 4 do
a + b + 1
end
end
The only change is that I added a guard and macro is unable to deal with that.
How can I improve MyMacro.defstate to make it work with a function with any number of guards?

If you inspect fn_atom with the defstate this_fails(a, b) when 1 < 2, you'll see that it's :when instead of :this_fails. This is because of how when expressions are represented in the Elixir AST:
iex(1)> quote do
...(1)> def foo, do: 1
...(1)> end
{:def, [context: Elixir, import: Kernel],
[{:foo, [context: Elixir], Elixir}, [do: 1]]}
iex(2)> quote do
...(2)> def foo when 1 < 2, do: 1
...(2)> end
{:def, [context: Elixir, import: Kernel],
[{:when, [context: Elixir],
[{:foo, [], Elixir}, {:<, [context: Elixir, import: Kernel], [1, 2]}]},
[do: 1]]}
You can fix this using some pattern matching:
defmodule MyMacro do
defmacro defstate(ast, do: block) do
f = case ast do
{:when, _, [{f, _, _} | _]} -> f
{f, _, _} -> f
end
quote do
def unquote(ast) do
IO.puts("LOGGING #{unquote(f)}")
unquote(block)
end
end
end
end
defmodule Test do
import MyMacro
defstate this_works(a, b) do
a + b + 1
end
defstate this_works_too(a, b) when a < 2 do
a + b + 1
end
end
defmodule A do
def main do
IO.inspect Test.this_works(1, 2)
IO.inspect Test.this_works_too(1, 2)
IO.inspect Test.this_works_too(3, 2)
end
end
A.main
Output:
LOGGING this_works
4
LOGGING this_works_too
4
** (FunctionClauseError) no function clause matching in Test.this_works_too/2
The following arguments were given to Test.this_works_too/2:
# 1
3
# 2
2
a.exs:24: Test.this_works_too/2
a.exs:33: A.main/0
(elixir) lib/code.ex:376: Code.require_file/2
(I also changed the unquote after def to make sure the when clause is preserved.)

The call to defstate is expanded at compile time to the things in the quote block from your defmacro. As such, guard expressions will not be applied to the macro call directly, because at compile time, the function you're defining inside is not called.
So you have to grab the :when tuple yourself and add the guards yourself:
defmodule MyMacro do
defmacro defstate({:when, _, [ast, guards]}, do: block) do
{fn_atom, _} = Macro.decompose_call(ast)
quote do
def unquote(fn_atom)(var!(a), var!(b)) when unquote(guards) do
IO.puts("LOGGING")
unquote(block)
end
end
end
end
Note how I match for a {:when, _, [ast, guards]} tuple now.
When you call a macro with a guard, it will put the original ast inside the first item of the arguments list, and the guard expression inside the second item.
Note that you'll still have to define a catch-all macro definition below this one in case you want to use your macro without guard clauses.

Related

Pass keyword argument to a macro in julia

I'd like to pass a keyword argument to a macro in julia. I try to parse the kwargs manually and build the Expr but got errors. Do I miss something?
macro sort(x, kv)
#show x,kv
d = []
if kv.head == :(=)
# push!(d, (kv.args[1], kv.args[2])) # fail
push!(d, (kv.args[1], esc(kv.args[2]))) # fail also
end
ex = quote
sort($x; $(d...))
end
Meta.show_sexpr(ex)
ex
end
x = collect(1:5)
rev=true
#sort x rev=rev
Output:
(x, kv) = (:x, :(rev = rev))
(:block,
(:line, 9, Symbol("REPL[36]")),
(:call, :sort, (:parameters, (:rev, :($(Expr(:escape, :rev))))), :x)
)ERROR: TypeError: non-boolean (Expr) used in boolean context
Stacktrace:
[1] (::Base.#kw##sort!)(::Array{Any,1}, ::Base.#sort!, ::Array{Int64,1}) at ./<missing>:0
[2] #sort#8(::Array{Any,1}, ::Function, ::Array{Int64,1}) at ./sort.jl:546
[3] (::Base.#kw##sort)(::Array{Any,1}, ::Base.#sort, ::Array{Int64,1}) at ./<missing>:0
[4] macro expansion at ./REPL[36]:9 [inlined]
[5] anonymous at ./<missing>:?
Edit: I am able to build keyword args using :parameters Expr. My generated Expr looks like the normal one. But strangely, I got the error "syntax: invalid syntax (parameters (kw (outerref rev) true))"
julia> macro m(f, x, ks...)
#show f x ks
ex = Expr(:call, f, x)
par = Expr(:parameters )
push!(ex.args, par)
for kv∈ks
k,v = kv.args
push!(par.args, Expr(:kw, k, v))
end
Meta.show_sexpr(ex)
ex
end
#m (macro with 1 method)
julia>
julia> Meta.show_sexpr(:(sort(1:9; rev=true)))
(:call, :sort, (:parameters, (:kw, :rev, true)), (:(:), 1, 9))
julia> #m sort 1:9 rev=true
f = :sort
x = :(1:9)
ks = (:(rev = true),)
(:call, :sort, (:(:), 1, 9), (:parameters, (:kw, :rev, true)))ERROR: syntax: invalid syntax (parameters (kw (outerref rev) true))

How to dynamically create modules with functions

On compilation stage I can easily produce functions with:
defmodule A1 do
defmodule A2 do
Enum.each %{m: 42}, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end
end
end
IO.puts A1.A2.m
#⇒ 42
Also, I can produce modules with functions from within a function call:
defmodule B1 do
def b2! do
defmodule B2 do
# enum is for the sake of future example
Enum.each %{m1: 42}, fn {_k, v} ->
# def b2(), do: unquote(v) WON’T WORK (WHY?), BUT
#v v
def b2(), do: #v
end
end
end
end
B1.b2! # produce a nested module
IO.puts B1.B2.b2 # call a method
#⇒ 42
Now my question is: how can I dynamically produce a module with dynamically created function names, e. g.:
defmodule B1 do
def b2! do
defmodule B2 do
Enum.each %{m1: 42, m2: 3.14}, fn {k, v} ->
#k k
#v v
def unquote(#k)(), do: #v # THIS DOESN’T WORK
end
end
end
end
NB I was able to achieve what I wanted with
defmodule B1 do
def b2! do
defmodule B2 do
Enum.each %{m1: 42, m2: 3.14}, fn {k, v} ->
ast = quote do: def unquote(k)(), do: unquote(v)
Code.eval_quoted(ast, [k: k, v: v], __ENV__)
end
end
end
end
but it seems to be quite hacky.
I believe this happens due to nested macro invocations (def and defmodule are both macros). If you place an unquote there, it unquotes from the top level def:
defmodule B1 do
k = :foo
v = :bar
def b2! do
defmodule B2 do
def unquote(k)(), do: unquote(v)
end
end
end
B1.b2!
IO.inspect B1.B2.foo
prints
:bar
The Module.create/3 recommends using that function to dynamically create modules when the body is an AST. With that, the code becomes much more elegant than the hacky solution using Code.eval_quoted/3:
defmodule B1 do
def b2! do
ast = for {k, v} <- %{m1: 42, m2: 3.14} do
quote do
def unquote(k)(), do: unquote(v)
end
end
Module.create(B1.B2, ast, Macro.Env.location(__ENV__))
end
end
B1.b2!
IO.inspect B1.B2.m1
IO.inspect B1.B2.m2
Output:
42
3.14

Howto dynamically generate function clauses

I need to dynamically generate function clauses, basing on the user’s config. For the sake of clarity, imagine I have a list of atoms:
#atoms ~w|foo bar baz|a
Coming from, say, config.exs. What I need is to generate this function (the MCVE is oversimplified, but it gives an impression on what I actually need):
#checker fn
{:foo, _} -> false
{:bar, _} -> false
{:baz, _} -> false
_ -> true
end
What I am currently doing is:
#clauses Enum.map(#atoms, fn tag ->
{:->, [], [[{:{}, [], [tag, {:_, [], Elixir}]}], false]}
end) ++ [{:->, [], [[{:_, [], Elixir}], true]}]
defmacrop checker, do: {:fn, [], #clauses}
It works pretty fine, but I expect I am overcomplicating things, missing something simple. So, my question is:
Is there an easy way to generate the function clauses in compile time?
I made it somewhat (see below for more) more readable using quote:
defmodule A do
#atoms ~w|foo bar baz|a
#clauses Enum.flat_map(#atoms, fn tag ->
quote do: ({unquote(tag), _} -> false)
end) ++ quote(do: (_ -> true))
defmacro checker, do: {:fn, [], #clauses}
end
defmodule B do
require A
f = A.checker
IO.inspect f.({:foo, :ok})
IO.inspect f.({:bar, :ok})
IO.inspect f.({:baz, :ok})
IO.inspect f.({:quux, :ok})
end
Output:
false
false
false
true
I expected quote(do: a -> b) to work, but it's a parse error right now so we have to do quote(do: (a -> b)) which wraps the quoted fragment we want in a list.
I also expected unquote to work inside fn when it's inside quote, but that also doesn't.
iex(1)> quote do
...(1)> fn
...(1)> unquote()
...(1)> _ -> true
...(1)> end
...(1)> end
** (SyntaxError) iex:2: expected clauses to be defined with -> inside: 'fn'
I believe these two are either bugs or missing features.

elixir string to method arguments in macro

I'm messing around with macros in elixir purely for fun and not profit :). So I have a comma separated string and I want to dynamically create a function with that list as arguments i.e.
defmacro __using__(opts) do
args = "a,b,c"
quote do
def test(unquote(args)) do
IO.inspect("#{a},#{b},#{c}")
end
end
end
The problem I have is that the method created is: test("1,2,3") and not test(1,2,3) for obvious reasons. So how do I convert a string into a method argument?
Chris
There's probably a better way but one way to convert "a,b,c" to something that can be injected into def's argument list would be to use Code.string_to_quoted!/1 and unquote_splicing:
defmacro __using__(_opts) do
args = "a,b,c"
args = Code.string_to_quoted!("[#{args}]")
quote do
def test(unquote_splicing(args)) do
IO.inspect(unquote(args))
end
end
end
Note that due to hygiene, you'll have to use args to access the variables and you cannot directly access a, b, or c inside the def.
Full Example:
defmodule Macros do
defmacro __using__(_opts) do
args = "a,b,c"
args = Code.string_to_quoted!("[#{args}]")
quote do
def test(unquote_splicing(args)) do
IO.inspect(unquote(args))
end
end
end
end
defmodule Main do
use Macros
def main do
test("a", "b", "c")
test(1, 2, 3)
end
end
Main.main
Output:
["a", "b", "c"]
[1, 2, 3]

Multiline function literal as arguments in Scala

I always wondered why sometimes with function literals we can ignore the curly brace even for multiple statements. To illustrate this, the syntax for a multiline function literal is to enclose the statements with curly braces. Like so,
val fl = (x: Int) => {
println("Add 25 to "+x)
x + 25
}
However, when you pass it to a single-argument function, you can ignore the required curly brace for the function literal.
So for a given function f,
def f( fl: Int => Int ) {
println("Result is "+ fl(5))
}
You can call f() like this,
f( x=> {
println("Add 25 to "+x)
x + 25
})
-------------------------
Add 25 to 5
Result: 30
Or when you use curly braces instead of parenthesis in the function call, you can remove the inner curly braces from the function literal. So the following code will also work,
f{ x=>
println("Add 25 to "+x)
x + 25
}
The above code is more readable and I notice that a lot of examples use this syntax. However, is there any special rule that I may have missed, to explain why this is working as intended?
There are just a couple of simple syntax rules. The appendix of the spec is worth perusing.
A function literal or anonymous function (6.23) will look like x => Expr or x => Block depending on whether the context is an Expr or a ResultExpr, respectively.
A function application (6.6) will look like f(Expr, Expr) or f BlockExpr, i.e., f{ Block }. That is, a BlockExpr is just a sequence of block statements inside {...}.
When you call f(g), then g is an Expr, so as a function literal, x => Expr. The Expr can be a BlockExpr, x => { ... }.
When you call f{ Block }, then f { x => ... } has the function literal in ResultExpr of a Block (which is just a sequence of statements, no braces required).
Here, it's obvious that the anon func is at the bottom of a block:
scala> def m(x: Int=>Int) = x(5)
m: (x: Int => Int)Int
scala> m {
| val y = 7
| x => // no brace
| x+y+1
| }
res0: Int = 13
This is one of the things that make Scala beautiful to me.
The simple answer to your question is:
Parentheses ( ) are meant for single line constructions. For instance, this works:
def f(fl: Int => Int) {
println("Result is " + fl(5))
}
f(
x =>
x + 25)
f(x => x + 25) // single line
and curly braces { } are meant for multiline statements. For instance, this works:
f {
x =>
println("Add 25 to " + x)
x + 25
}
but this code doesn't work:
f (
x =>
println("Add 25 to " + x)
x + 25
)
The compiler complains with the following message:
value x is not a member of Unit possible cause: maybe a semicolon is
missing before `value x'?
If you add the semicolon, you'll get a syntax error caused by the unmatched parenthesis.
If you try to do this:
f { x => println("Add 25 to " + x) x + 25 }
The compiler will get back to you with the message:
value x is not a member of unit
Do you get that he is trying to find x as a member of unit. Like:
f { println("Add 25 to " + x).x.+(25) }
Which is clearly wrong.
If you add the inner curly braces, Like this:
f (
x => {
println("Add 25 to " + x)
x + 25
}
)
This will also work, but you still have a multiline statement that is signaled by the usage of the curly braces. So the compiler knows what you want there is to first print and then add 25 to x.
I've been bitten before by those subtleties. Ever since, I've been paying attention to the way I code with those, because you'll code and read a lot of this when you're mainly using maps, flatMaps, foreachs, fors and currying.
Cheers!