Koska makrot ovat ennen kaikkea koodin esikäsittelyä, niiden inputit ovat rajalliset, sillä ohjelmakoodia ei ole suoritettu ennen makroa. Makro voi ottaa input argumentteina lähinnä numeroita ja merkkijonoja.
Tehdään yksinkertainen makro joka palauttaa inputin tyypin ja sisällön. Tällä voi helposti tutkia minkälaisia asioita makro “syö”:
macro showargs(args...)
for arg in args
@show typeof(arg), arg
end
return
end
@showargs(1, 1.0, true, a, "hei")
(typeof(arg), arg) = (Int64, 1)
(typeof(arg), arg) = (Float64, 1.0)
(typeof(arg), arg) = (Bool, true)
(typeof(arg), arg) = (Symbol, :a)
(typeof(arg), arg) = (String, "hei")
Listasta voi jotakin primitiivityyppejä puuttua, mutta tässä nämä pitkälti on. Kaikenlaiset monimutkaisemmat rakenteet eivät ole sellaisenaan käytettävissä makrossa sisällä, vaan ne menevät sisälle Expr-tyyppinä:
struct Foo end
@showargs(
[1, 2, 3],
:(a => 1),
(1, 2, 3),
(a = 1, b = 2),
Dict(1 => 2),
1 + 2,
a + b,
f(x) = x^2,
rand(2, 2),
Foo())
(typeof(arg), arg) = (Expr, :([1, 2, 3]))
(typeof(arg), arg) = (Expr, :(:(a => 1)))
(typeof(arg), arg) = (Expr, :((1, 2, 3)))
(typeof(arg), arg) = (Expr, :((a = 1, b = 2)))
(typeof(arg), arg) = (Expr, :(Dict(1 => 2)))
(typeof(arg), arg) = (Expr, :(1 + 2))
(typeof(arg), arg) = (Expr, :(a + b))
(typeof(arg), arg) = (Expr, :(f(x) = begin
x ^ 2
end))
(typeof(arg), arg) = (Expr, :(rand(2, 2)))
(typeof(arg), arg) = (Expr, :(Foo()))
Makroon voidaan myös laittaa begin .. end
lohkon sisällä
ohjelmakoodia, joka mahdollistaa periaatteessa pienimuotoisen
konfiguraation tekemisen ennen varsinaista toiminallisuuta:
@showargs begin
a = 1
b = 2.0
c = true
d = "hei"
e = [1, 2]
f = (a => 1)
end
(typeof(arg), arg) = (Expr, quote
a = 1
b = 2.0
c = true
d = "hei"
e = [1, 2]
f = a => 1
end)
Myös key-value-pareja voi antaa, joka voisi olla makron konfiguroinnin lähtöpiste:
@showargs(
coordinates = (1, 2),
dims = 2,
P = :(1, :u, :u^2))
(typeof(arg), arg) = (Expr, :(coordinates = (1, 2)))
(typeof(arg), arg) = (Expr, :(dims = 2))
(typeof(arg), arg) = (Expr, :(P = :((1, :u, :u ^ 2))))
Jos etsitään jotakin monimutkaisempaa, niin quote-tyyppinen ratkaisu voisi olla se paras tapa, sillä voidaan itse asiassa kirjoittaa ihan normaalia Julia-koodia joka sitten ajetaan makron ajovaiheessa eikä lopputuotteessa:
macro do_something_with_config(config)
c = Module()
Core.eval(c, config)
return quote
println("element family = ", $(c.element_family))
println("c = ", $(c.c))
end
end
@do_something_with_config begin
element_family = "Lagrange"
V = [1 2; 2 1]
e = [1, 2]
c = V * e
end
element family = Lagrange
c = [5, 4]
@macroexpand
-komennolla voidaan taas tarkastaa, mitä makro oikeastaan
tekee:
@macroexpand @do_something_with_config begin
element_family = "Lagrange"
V = [1 2; 2 1]
e = [1, 2]
c = V * e
end
quote
Main.println("element family = ", "Lagrange")
Main.println("c = ", [5, 4])
end
“Normaali” argumentin syöttäminen makroon sisälle ei toimi. Näin voi tehdä:
macro evalf(f, vars)
@show f
return :(($vars) -> $f)
end
@macroexpand @evalf 1 + u + v + 2*u*v (u, v)
f = :(1 + u + v + 2 * u * v)
:((var"#4#u", var"#5#v")->begin
1 + var"#4#u" + var"#5#v" + 2 * var"#4#u" * var"#5#v"
end)
Mutta ei näin:
expr = :(1 + u + v)
@macroexpand @evalf expr (u, v)
f = :expr
:((var"#6#u", var"#7#v")->begin
Main.expr
end)
Makro on tietoinen omasta ympäristöstä:
macro evalf2(f)
@show f
return :($f)
end
@macroexpand @evalf2 1 + u + v + 2*u*v
f = :(1 + u + v + 2 * u * v)
:(1 + Main.u + Main.v + 2 * Main.u * Main.v)
Evaluointi yrittää käyttää globaaleja muuttujia, koska u
tai v
ei
ole määritelty makrossa. Mikäli ne määritellään makron sisällä, kaikki
toimii kuten pitääkin.
macro evalf3(f)
@show f
return quote
u = 1
v = 2
$f
end
end
@macroexpand @evalf3 1 + u + v + 2*u*v
f = :(1 + u + v + 2 * u * v)
quote
var"#10#u" = 1
var"#11#v" = 2
1 + var"#10#u" + var"#11#v" + 2 * var"#10#u" * var"#11#v"
end
Evaluointi tapahtuu aina globaalilla tasolla riippumatta onko makroa käytetty funktion sisällä:
function do_evalf2()
u = 1
v = 2
@evalf2 u + v
end
do_evalf2()
f = :(u + v)
UndefVarError: u not defined
Stacktrace:
[1] do_evalf2() at ./In[12]:4
[2] top-level scope at In[12]:6
Jos määritellään u ja v globaalisti niin homma toimii:
u = 5
v = 10
do_evalf2()
15
Näin siksi, että u
ja v
ovat itse asiassa Main.u
ja Main.v
.
Juliasta löytyy @eval
-makro suoraan, jota voi käyttää Expr-tyyppien
evaluoimiseen. Esimerkiksi:
expr = :(1 + u + v + u*v)
@eval h(u, v) = $expr
h(1, 2)
6
Expr voidaan myös laskea ja palauttaa funktiosta, joka voidaan sitten evaluoida inline-funktioksi:
function get_stuff()
return :(t(u, v) = 99)
end
@eval $(get_stuff())
t(2, 3)
99
Kaikki voidaan myös tehdä funktion sisällä, mutta evaluointi tapahtuu globaaliin ympäristöön:
function gen_func(fname, fval)
fname2 = Symbol("$fname$fname")
fval2 = 2*fval
expr = :($fname2() = $fval2)
@eval $expr
end
gen_func(:testi, 123)
testitesti()
246