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]
Related
I am a beginner exploring scala.The following is a Scala function.
def printArray[K](array:Array[K]) = array.mkString("Array(" , ", " , ")")
val array2 = Array("a", 2, true)
printArray(array2)
The output is
Array(a, 2, true)
My doubts
Here we have given the array type as K. What does K means? Does it mean all types?
How is the fucntion 'mkString' able to give the output as Array(a, 2, true).
Basically I don't understand the concatenation part.
Appreciate your help.
The mkString method called as
arr.mkString(prefix, separator, suffix)
will invoke toString on all array elements, prepend the prefix, then concatenate all strings separating them by the separator, and finally append the suffix.
The type parameter K in printArray[K] is ignored, it could be replaced by an existential. It's just a method with a bad name and confusing signature.
When you store any primitive data types (like Int) together with types that extend AnyRef (like String) into the same array, the least upper bound is inferred to be Any, so in
printArray(array2)
the K is set to Any, and the mkString works as described above, gluing together
Array( prefix
a "a".toString
, separator
2 2.toString
, separator
true true.toString
) suffix
yielding the string Array(a,2,true).
K is not a type here it is a type parameter, for more intuition have a look at other question Type parameter in scala
In this specific example K is infered to by Any - the most specific type that satisfies all 3 values "a", 2 and true
val array2: Array[Any] = Array("a", 2, true)
the mkString function joins all items of collection into single string. It adds separator between items and some strings in the beginning and end. Documentation mkString
If you look at your array2 definition in REPL, you will see that array2 is of type Any, the parent type of all the other types in Scala
scala> val array2 = Array("a", 2, true)
//array2: Array[Any] = Array(a, 2, true)
So when you call the function def printArray[K](array:Array[K]) = array.mkString("Array(" , ", " , ")") K now is treated as Any which returns a string with intitial String as Array( and ending string as ) and all the values separated by ,.
def mkString(start: String, sep: String, end: String): String =
addString(new StringBuilder(), start, sep, end).toString
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.
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
I am new to scala..I came across a concept where it says like below:
{ val x = a; b.:::(x) }
In this block a is still evaluated before b, and then the result of
this evaluation is passed as an operand to b’s ::: method
What is the meaning of above statement..
I tried like below:
var a =10
var b =20
What should be the result i should expect.
Can somebody please give me an example...
Thanks in advance....
The ::: operator is defined on List trait and concatenates two lists. Using it on Int like in your example (var a=10) shouldn't work (unless you define such operator yourself).
Here is how it works on lists:
val a = List(1, 2);
val b = List(3, 4);
val c1 = a ::: b // List(1, 2, 3, 4)
val c2 = a.:::(b) // List(3, 4, 1, 2)
Calling ::: with the infix syntax (c1) and method call syntax (c2) differ in the order in which lists are concatenated (see Jörg's comment).
The statement "a is still evaluated before b" means that a is evaluated before passing it as an argument to the method call. Unless the method uses call by name, its arguments are evaluated before the call just like in Java.
This could give you some hint how to search for meaning of Scala operators and keywords.
How to refer to the argument of a macro in the definition of some other macro?
For eg, FOO(abc) is a macro. Suppose I want to write some other macro 'BAR' which is checking the argument of FOO first and if it 'abc', expands to one line and if not, expands to some other line.
BAR(if FOO_arg is abc)
{do this}
else
{do this}
How can it be achieved using m4 macros?