Julia Language
Metaprogramacion
Buscar..
Sintaxis
- nombre de macro (ex) ... fin
- cita ... fin
- : (...)
- $ x
- Meta.quot (x)
- QuoteNode (x)
- esc (x)
Observaciones
Las funciones de metaprogramación de Julia están muy inspiradas en las de lenguajes similares a Lisp, y les parecerán familiares a las personas con algún fondo Lisp. La metaprogramación es muy potente. Cuando se usa correctamente, puede llevar a un código más conciso y legible.
La quote ... end
es sintaxis quasiquote. En lugar de las expresiones dentro de ser evaluadas, simplemente se analizan. El valor de la quote ... end
expresión quote ... end
es el árbol de sintaxis abstracta (AST) resultante.
La sintaxis :(...)
es similar a la sintaxis de quote ... end
, pero es más ligera. Esta sintaxis es más concisa que la quote ... end
.
Dentro de una quasiquote, el operador $
es especial e interpola su argumento en el AST. Se espera que el argumento sea una expresión que se empalma directamente en el AST.
La función Meta.quot(x)
cita su argumento. Esto suele ser útil en combinación con el uso de $
para la interpolación, ya que permite que las expresiones y los símbolos se empalen literalmente en el AST.
Reimplementando la macro @show
En Julia, la macro @show
suele ser útil para fines de depuración. Muestra tanto la expresión a evaluar como su resultado, y finalmente devuelve el valor del resultado:
julia> @show 1 + 1
1 + 1 = 2
2
Es sencillo crear nuestra propia versión de @show
:
julia> macro myshow(expression)
quote
value = $expression
println($(Meta.quot(expression)), " = ", value)
value
end
end
Para usar la nueva versión, simplemente use la macro @myshow
:
julia> x = @myshow 1 + 1 1 + 1 = 2 2 julia> x 2
Hasta bucle
Todos estamos acostumbrados a la sintaxis while
, que ejecuta su cuerpo mientras la condición se evalúa como true
. ¿Qué true
si queremos implementar un bucle until
, que ejecuta un bucle hasta que la condición se evalúe como true
?
En Julia, podemos hacer esto creando una macro @until
, que se detiene para ejecutar su cuerpo cuando se cumple la condición:
macro until(condition, expression) quote while !($condition) $expression end end |> esc end
Aquí hemos utilizado la sintaxis de encadenamiento de funciones |>
, que es equivalente a llamar a la función esc
en todo el bloque de quote
. La función esc
evita que la macro higiene se aplique a los contenidos de la macro; sin él, las variables con ámbito en la macro serán renombradas para evitar colisiones con variables externas. Vea la documentación de Julia sobre macro higiene para más detalles.
Puede usar más de una expresión en este bucle, simplemente colocando todo dentro de un bloque begin ... end
:
julia> i = 0; julia> @until i == 10 begin println(i) i += 1 end 0 1 2 3 4 5 6 7 8 9 julia> i 10
QuoteNode, Meta.quot y Expr (: quote)
Hay tres formas de citar algo usando una función de Julia:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
¿Qué significa "citar" y para qué sirve? Las citas nos permiten proteger las expresiones para que no sean interpretadas como formas especiales por Julia. Un caso de uso común es cuando generamos expresiones que deben contener elementos que se evalúan como símbolos. (Por ejemplo, esta macro necesita devolver una expresión que se evalúe como un símbolo). No funciona simplemente para devolver el símbolo:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
¿Que está pasando aqui? @mysym
expande a :x
, que como expresión se interpreta como la variable x
. Pero aún no se ha asignado nada a x
, por lo que obtenemos un error x not defined
.
Para solucionar esto, debemos citar el resultado de nuestra macro:
julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)
julia> @mysym2
:x
julia> macroexpand(:(@mysym2))
:(:x)
Aquí, hemos utilizado la función Meta.quot
para convertir nuestro símbolo en un símbolo entre comillas, que es el resultado que queremos.
¿Cuál es la diferencia entre Meta.quot
y QuoteNode
, y cuál debo usar? En casi todos los casos, la diferencia realmente no importa. Quizás sea un poco más seguro a veces usar QuoteNode
lugar de Meta.quot
. Sin embargo, explorar la diferencia es informativo sobre cómo funcionan las expresiones y macros de Julia.
La diferencia entre Meta.quot
y QuoteNode
, explicada
Aquí hay una regla de oro:
- Si necesita o desea admitir la interpolación, use
Meta.quot
; - Si no puede o no quiere permitir la interpolación, use
QuoteNode
.
En resumen, la diferencia es que Meta.quot
permite la interpolación dentro de lo Meta.quot
, mientras que QuoteNode
protege su argumento de cualquier interpolación. Para entender la interpolación, es importante mencionar la expresión $
. Hay un tipo de expresión en Julia llamada expresión $
. Estas expresiones permiten escapar. Por ejemplo, considere la siguiente expresión:
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Cuando se evalúa, esta expresión evaluará 1
y lo asignará a x
, luego construirá una expresión de la forma _ + _
donde _
se reemplazará por el valor de x
. Por lo tanto, el resultado de esto debe ser la expresión 1 + 1
(que aún no se ha evaluado, y por lo tanto es distinta del valor 2
). De hecho, este es el caso:
julia> eval(ex)
:(1 + 1)
Digamos ahora que estamos escribiendo una macro para construir este tipo de expresiones. Nuestra macro tomará un argumento, que reemplazará el 1
en el ex
anterior. Este argumento puede ser cualquier expresión, por supuesto. Aquí hay algo que no es exactamente lo que queremos:
julia> macro makeex(arg)
quote
:( x = $(esc($arg)); :($x + $x) )
end
end
@makeex (macro with 1 method)
julia> @makeex 1
quote
x = $(Expr(:escape, 1))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> @makeex 1 + 1
quote
x = $(Expr(:escape, 2))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
El segundo caso es incorrecto, porque deberíamos mantener 1 + 1
evaluar. Meta.quot
eso citando el argumento con Meta.quot
:
julia> macro makeex2(arg)
quote
:( x = $$(Meta.quot(arg)); :($x + $x) )
end
end
@makeex2 (macro with 1 method)
julia> @makeex2 1 + 1
quote
x = 1 + 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
La higiene macro no se aplica al contenido de una cotización, por lo que no es necesario escapar en este caso (y de hecho no es legal) en este caso.
Como se mencionó anteriormente, Meta.quot
permite la interpolación. Así que vamos a intentarlo:
julia> @makeex2 1 + $(sin(1))
quote
x = 1 + 0.8414709848078965
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> let q = 0.5
@makeex2 1 + $q
end
quote
x = 1 + 0.5
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Del primer ejemplo, vemos que la interpolación nos permite alinear el sin(1)
, en lugar de hacer que la expresión sea un sin(1)
literal sin(1)
. El segundo ejemplo muestra que esta interpolación se realiza en el ámbito de invocación de macros, no en el propio ámbito de la macro. Eso es porque nuestra macro no ha evaluado ningún código; todo lo que está haciendo es generar código. La evaluación del código (que se abre camino en la expresión) se realiza cuando la expresión que genera la macro se ejecuta realmente.
¿Y si hubiéramos usado QuoteNode
lugar? Como puede imaginar, dado que QuoteNode
evita que la interpolación ocurra, esto significa que no funcionará.
julia> macro makeex3(arg)
quote
:( x = $$(QuoteNode(arg)); :($x + $x) )
end
end
@makeex3 (macro with 1 method)
julia> @makeex3 1 + $(sin(1))
quote
x = 1 + $(Expr(:$, :(sin(1))))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> let q = 0.5
@makeex3 1 + $q
end
quote
x = 1 + $(Expr(:$, :q))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> eval(@makeex3 $(sin(1)))
ERROR: unsupported or misplaced expression $
in eval(::Module, ::Any) at ./boot.jl:234
in eval(::Any) at ./boot.jl:233
En este ejemplo, podríamos estar de acuerdo en que Meta.quot
ofrece una mayor flexibilidad, ya que permite la interpolación. Entonces, ¿por qué podríamos considerar usar QuoteNode
? En algunos casos, es posible que no deseemos realmente la interpolación, y que realmente queramos la expresión $
literal. ¿Cuándo sería deseable? Consideremos una generalización de @makeex
donde podemos pasar argumentos adicionales que determinan lo que viene a la izquierda y derecha del signo +
:
julia> macro makeex4(expr, left, right)
quote
quote
$$(Meta.quot(expr))
:($$$(Meta.quot(left)) + $$$(Meta.quot(right)))
end
end
end
@makeex4 (macro with 1 method)
julia> @makeex4 x=1 x x
quote # REPL[110], line 4:
x = 1 # REPL[110], line 5:
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> eval(ans)
:(1 + 1)
Una limitación de nuestra implementación de @makeex4
es que no podemos usar expresiones como los lados izquierdo y derecho de la expresión directamente, ya que se interpolan. En otras palabras, las expresiones pueden evaluarse para interpolación, pero es posible que deseamos que se conserven. (Ya que hay muchos niveles de cotización y evaluación aquí, aclaremos: nuestra macro genera código que construye una expresión que cuando se evalúa produce otra expresión . ¡Uf!)
julia> @makeex4 x=1 x/2 x
quote # REPL[110], line 4:
x = 1 # REPL[110], line 5:
$(Expr(:quote, :($(Expr(:$, :(x / 2))) + $(Expr(:$, :x)))))
end
julia> eval(ans)
:(0.5 + 1)
Deberíamos permitir que el usuario especifique cuándo debe ocurrir la interpolación y cuándo no. Teóricamente, es una solución fácil: solo podemos eliminar uno de los $
signos en nuestra aplicación y dejar que el usuario contribuya con los suyos. Lo que esto significa es que interpolamos una versión citada de la expresión ingresada por el usuario (que ya hemos citado e interpolado una vez). Esto lleva al siguiente código, que puede ser un poco confuso al principio, debido a los múltiples niveles anidados de cotización y no cotización. Intenta leer y entender para qué sirve cada escape.
julia> macro makeex5(expr, left, right)
quote
quote
$$(Meta.quot(expr))
:($$(Meta.quot($(Meta.quot(left)))) + $$(Meta.quot($(Meta.quot(right)))))
end
end
end
@makeex5 (macro with 1 method)
julia> @makeex5 x=1 1/2 1/4
quote # REPL[121], line 4:
x = 1 # REPL[121], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end
julia> eval(ans)
:(1 / 2 + 1 / 4)
julia> @makeex5 y=1 $y $y
ERROR: UndefVarError: y not defined
Las cosas empezaron bien, pero algo salió mal. El código generado de la macro está intentando interpolar la copia de y
en el ámbito de invocación de la macro; pero no hay copia de y
en el ámbito de invocación de macros. Nuestro error es permitir la interpolación con los argumentos segundo y tercero en la macro. Para corregir este error, debemos utilizar QuoteNode
.
julia> macro makeex6(expr, left, right)
quote
quote
$$(Meta.quot(expr))
:($$(Meta.quot($(QuoteNode(left)))) + $$(Meta.quot($(QuoteNode(right)))))
end
end
end
@makeex6 (macro with 1 method)
julia> @makeex6 y=1 1/2 1/4
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end
julia> eval(ans)
:(1 / 2 + 1 / 4)
julia> @makeex6 y=1 $y $y
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end
julia> eval(ans)
:(1 + 1)
julia> @makeex6 y=1 1+$y $y
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 + $(Expr(:$, :y)))))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end
julia> @makeex6 y=1 $y/2 $y
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)) / 2)))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end
julia> eval(ans)
:(1 / 2 + 1)
Al utilizar QuoteNode
, hemos protegido nuestros argumentos de la interpolación. Como QuoteNode
solo tiene el efecto de protecciones adicionales, nunca es dañino usar QuoteNode
, a menos que desee interpolación. Sin embargo, entender la diferencia hace posible entender dónde y por qué Meta.quot
podría ser una mejor opción.
Este ejercicio largo es con un ejemplo que es claramente demasiado complejo para aparecer en cualquier aplicación razonable. Por lo tanto, hemos justificado la siguiente regla de oro, mencionada anteriormente:
- Si necesita o desea admitir la interpolación, use
Meta.quot
; - Si no puede o no quiere permitir la interpolación, use
QuoteNode
.
¿Qué hay de Expr (: cita)?
Expr(:quote, x)
es equivalente a Meta.quot(x)
. Sin embargo, este último es más idiomático y es el preferido. Para el código que utiliza mucho la metaprogramación, a menudo se usa una línea que using Base.Meta
, lo que permite hacer referencia a Meta.quot
como simplemente quot
.
Guía
Metaprogramación bits y bobs de π
Metas:
Enseñar a través de ejemplos funcionales / útiles / no abstractos dirigidos mínimos (por ejemplo,
@swap
o@assert
) que introducen conceptos en contextos adecuadosPrefiero dejar que el código ilustre / demuestre los conceptos en lugar de los párrafos explicativos.
Evite vincular la 'lectura requerida' a otras páginas, ya que interrumpe la narrativa
Presente las cosas en un orden sensible que facilitará el aprendizaje.
Recursos:
julialang.org
wikibook (@Cormullion)
5 capas (Leah Hanson)
Cotización SO-Doc (@TotalVerb)
SO-Doc - Símbolos que no son identificadores legales (@TotalVerb)
SO: ¿Qué es un símbolo en Julia (@StefanKarpinski)
Hilo del discurso (@ pi-) Metaprogramación
La mayor parte del material provino del canal del discurso, la mayor parte de eso provino de fcard ... por favor, pídeme si me he olvidado de las atribuciones.
Símbolo
julia> mySymbol = Symbol("myName") # or 'identifier' :myName julia> myName = 42 42 julia> mySymbol |> eval # 'foo |> bar' puts output of 'foo' into 'bar', so 'bar(foo)' 42 julia> :( $mySymbol = 1 ) |> eval 1 julia> myName 1
Pasando banderas en funciones:
function dothing(flag) if flag == :thing_one println("did thing one") elseif flag == :thing_two println("did thing two") end end julia> dothing(:thing_one) did thing one julia> dothing(:thing_two) did thing two
Un ejemplo de hashkey:
number_names = Dict{Symbol, Int}() number_names[:one] = 1 number_names[:two] = 2 number_names[:six] = 6
(Avanzado) (@fcard) :foo
aka :(foo)
produce un símbolo si foo
es un identificador válido, de lo contrario es una expresión.
# NOTE: Different use of ':' is: julia> :mySymbol = Symbol('hello world') #(You can create a symbol with any name with Symbol("<name>"), # which lets us create such gems as: julia> one_plus_one = Symbol("1 + 1") Symbol("1 + 1") julia> eval(one_plus_one) ERROR: UndefVarError: 1 + 1 not defined ... julia> valid_math = :($one_plus_one = 3) :(1 + 1 = 3) julia> one_plus_one_plus_two = :($one_plus_one + 2) :(1 + 1 + 2) julia> eval(quote $valid_math @show($one_plus_one_plus_two) end) 1 + 1 + 2 = 5 ...
Básicamente, puedes tratar a los Símbolos como cadenas ligeras. Eso no es para lo que son, pero puedes hacerlo, entonces ¿por qué no? La propia Base de Julia lo hace, print_with_color(:red, "abc")
imprime un abc de color rojo.
Expr (AST)
(Casi) todo en Julia es una expresión, es decir, una instancia de Expr
, que contendrá un AST .
# when you type ... julia> 1+1 2 # Julia is doing: eval(parse("1+1")) # i.e. First it parses the string "1+1" into an `Expr` object ... julia> ast = parse("1+1") :(1 + 1) # ... which it then evaluates: julia> eval(ast) 2 # An Expr instance holds an AST (Abstract Syntax Tree). Let's look at it: julia> dump(ast) Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol + 2: Int64 1 3: Int64 1 typ: Any # TRY: fieldnames(typeof(ast)) julia> :(a + b*c + 1) == parse("a + b*c + 1") == Expr(:call, :+, :a, Expr(:call, :*, :b, :c), 1) true
Anidado Expr
s:
julia> dump( :(1+2/3) ) Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol + 2: Int64 1 3: Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol / 2: Int64 2 3: Int64 3 typ: Any typ: Any # Tidier rep'n using s-expr julia> Meta.show_sexpr( :(1+2/3) ) (:call, :+, 1, (:call, :/, 2, 3))
Expr
multiline usando quote
julia> blk = quote x=10 x+1 end quote # REPL[121], line 2: x = 10 # REPL[121], line 3: x + 1 end julia> blk == :( begin x=10; x+1 end ) true # Note: contains debug info: julia> Meta.show_sexpr(blk) (:block, (:line, 2, Symbol("REPL[121]")), (:(=), :x, 10), (:line, 3, Symbol("REPL[121]")), (:call, :+, :x, 1) ) # ... unlike: julia> noDbg = :( x=10; x+1 ) quote x = 10 x + 1 end
... por lo que quote
es funcionalmente lo mismo pero proporciona información de depuración adicional.
(*) CONSEJO : Use let
para mantener x
dentro del bloque
quote
una quote
Expr(:quote, x)
se utiliza para representar comillas dentro de comillas.
Expr(:quote, :(x + y)) == :(:(x + y))
Expr(:quote, Expr(:$, :x)) == :(:($x))
QuoteNode(x)
es similar a Expr(:quote, x)
pero evita la interpolación.
eval(Expr(:quote, Expr(:$, 1))) == 1
eval(QuoteNode(Expr(:$, 1))) == Expr(:$, 1)
( Desambigüe los diversos mecanismos de cotización en metaprogramación de Julia
¿Son $ y : (...) de alguna manera inversos entre sí?
:(foo)
significa "no mire el valor, mire la expresión" $foo
significa "cambie la expresión a su valor"
:($(foo)) == foo
. $(:(foo))
es un error. $(...)
no es una operación y no hace nada por sí misma, es un "interpolar esto". señal de que utiliza la sintaxis de cotización. Es decir, solo existe dentro de una cotización.
¿Es $
foo
lo mismo que eval(
foo
)
?
¡No! $foo
se intercambia por el valor en tiempo de compilación eval(foo)
significa hacerlo en tiempo de ejecución
eval
se producirá en el ámbito global, la interpolación es local.
eval(:<expr>)
debe devolver lo mismo que solo <expr>
(asumiendo que <expr>
es una expresión válida en el espacio global actual)
eval(:(1 + 2)) == 1 + 2
eval(:(let x=1; x + 1 end)) == let x=1; x + 1 end
macro
s
Listo? :)
# let's try to make this! julia> x = 5; @show x; x = 5
Vamos a hacer nuestra propia macro @show
:
macro log(x) :( println( "Expression: ", $(string(x)), " has value: ", $x ) ) end u = 42 f = x -> x^2 @log(u) # Expression: u has value: 42 @log(42) # Expression: 42 has value: 42 @log(f(42)) # Expression: f(42) has value: 1764 @log(:u) # Expression: :u has value: u
expand
para bajar un Expr
5 capas (Leah Hanson) <- explica cómo Julia toma el código fuente como una cadena, lo tokeniza en un Expr
-tree (AST), expande todas las macros (aún AST), baja (AST rebajado), luego se convierte en LLVM (y más allá, en este momento no necesitamos preocuparnos por lo que hay más allá)
Q: code_lowered
actúa sobre las funciones. ¿Es posible bajar un Expr
? A: siup!
# function -> lowered-AST julia> code_lowered(*,(String,String)) 1-element Array{LambdaInfo,1}: LambdaInfo template for *(s1::AbstractString, ss::AbstractString...) at strings/basic.jl:84 # Expr(i.e. AST) -> lowered-AST julia> expand(:(x ? y : z)) :(begin unless x goto 3 return y 3: return z end) julia> expand(:(y .= x.(i))) :((Base.broadcast!)(x,y,i)) # 'Execute' AST or lowered-AST julia> eval(ast)
Si solo quieres expandir macros puedes usar macroexpand
:
# AST -> (still nonlowered-)AST but with macros expanded: julia> macroexpand(:(@show x)) quote (Base.println)("x = ",(Base.repr)(begin # show.jl, line 229: #28#value = x end)) #28#value end
... que devuelve un AST no bajado pero con todas las macros expandidas.
esc()
esc(x)
devuelve un Expr que dice "no aplique higiene a esto", es lo mismo que Expr(:escape, x)
. La higiene es lo que mantiene una macro autocontenida, y tú esc
cosas si quieres que se "escapen". p.ej
Ejemplo: swap
macro para ilustrar esc()
macro swap(p, q) quote tmp = $(esc(p)) $(esc(p)) = $(esc(q)) $(esc(q)) = tmp end end x,y = 1,2 @swap(x,y) println(x,y) # 2 1
$
nos permite 'escapar' de la quote
. Entonces, ¿por qué no simplemente $p
y $q
? es decir
# FAIL! tmp = $p $p = $q $q = tmp
Como eso buscaría primero el alcance de la macro
para p
, y encontraría un p
local, es decir, el parámetro p
(sí, si posteriormente accede a p
sin esc
-ing, la macro considera el parámetro p
como una variable local).
Entonces $p = ...
es solo una asignación a la p
local. no afecta a ninguna variable que se haya pasado en el contexto de llamada.
Ok, entonces, ¿qué tal
# Almost! tmp = $p # <-- you might think we don't $(esc(p)) = $q # need to esc() the RHS $(esc(q)) = tmp
Así que esc(p)
está 'filtrando' p
en el contexto de llamada. "Lo que se pasó a la macro que recibimos como p
"
julia> macro swap(p, q) quote tmp = $p $(esc(p)) = $q $(esc(q)) = tmp end end @swap (macro with 1 method) julia> x, y = 1, 2 (1,2) julia> @swap(x, y); julia> @show(x, y); x = 2 y = 1 julia> macroexpand(:(@swap(x, y))) quote # REPL[34], line 3: #10#tmp = x # REPL[34], line 4: x = y # REPL[34], line 5: y = #10#tmp end
Como se puede ver tmp
recibe el tratamiento de la higiene #10#tmp
, mientras que x
e y
no lo hacen. Julia está creando un identificador único para tmp
, algo que puedes hacer manualmente con gensym
, es decir:
julia> gensym(:tmp) Symbol("##tmp#270")
Pero: hay un gotcha:
julia> module Swap export @swap macro swap(p, q) quote tmp = $p $(esc(p)) = $q $(esc(q)) = tmp end end end Swap julia> using Swap julia> x,y = 1,2 (1,2) julia> @swap(x,y) ERROR: UndefVarError: x not defined
Otra cosa que hace la higiene macro de julia es que, si la macro es de otro módulo, crea cualquier variable (que no fue asignada dentro de la expresión de retorno de la macro, como tmp
en este caso) globales del módulo actual, por lo que $p
convierte en Swap.$p
, igualmente $q
-> Swap.$q
.
En general, si necesita una variable que está fuera del alcance de la macro, debe esclatarla, por lo que debe esc(p)
y esc(q)
independientemente de si están en el LHS o RHS de una expresión, o incluso por sí mismas.
la gente ya ha mencionado gensym
varias veces y pronto te dejará seducir por el lado oscuro de la omisión para escapar de toda la expresión con unos cuantos gensym
s aquí y allá, pero ... Asegúrate de entender cómo funciona la higiene antes de intentar ser más inteligente que eso! No es un algoritmo particularmente complejo, por lo que no debería tomar mucho tiempo, ¡pero no lo apresures! No uses ese poder hasta que entiendas todas las ramificaciones de él ... (@fcard)
Ejemplo: until
macro
(@ Ismael-VC)
"until loop" macro until(condition, block) quote while ! $condition $block end end |> esc end julia> i=1; @until( i==5, begin; print(i); i+=1; end ) 1234
(@fcard) |>
es controvertido, sin embargo. Me sorprende que una mafia no haya venido a discutir todavía. (tal vez todo el mundo está cansado de eso). Hay una recomendación de tener la mayoría, si no toda la macro, solo una llamada a una función, así que:
macro until(condition, block) esc(until(condition, block)) end function until(condition, block) quote while !$condition $block end end end
... es una alternativa más segura.
## @ el simple desafío macro de fcard
Tarea: intercambiar los operandos, entonces los swaps(1/2)
dan 2.00
es decir, 2/1
macro swaps(e) e.args[2:3] = e.args[3:-1:2] e end @swaps(1/2) 2.00
Más desafíos macro de @fcard aquí
Interpolación y assert
macro.
http://docs.julialang.org/en/release-0.5/manual/metaprogramming/#building-an-advanced-macro
macro assert(ex) return :( $ex ? nothing : throw(AssertionError($(string(ex)))) ) end
Q: ¿Por qué los últimos $
? R: Interpola, es decir, obliga a Julia a eval
esa string(ex)
medida que la ejecución pasa a través de la invocación de esta macro. es decir, si solo ejecuta ese código no forzará ninguna evaluación. Pero en el momento en que assert(foo)
Julia invocará esta macro reemplazando su 'AST token / Expr' con lo que sea que devuelva, y el $
entrará en acción.
Un truco divertido para usar {} para bloques.
(@fcard) No creo que haya nada técnico que evite el uso de {}
como bloques, de hecho, se puede incluso aplicar un punteo en la sintaxis residual {}
para que funcione:
julia> macro c(block) @assert block.head == :cell1d esc(quote $(block.args...) end) end @c (macro with 1 method) julia> @c { print(1) print(2) 1+2 } 123
* (no es probable que siga funcionando si / cuando la sintaxis {} está reutilizada)
Entonces, primero Julia ve el token de macro, así que leerá / analizará tokens hasta el end
correspondiente, ¿y creará qué? Un Expr
con .head=:macro
o algo? ¿Almacena "a+1"
como una cadena o lo divide en :+(:a, 1)
? ¿Cómo ver?
?
(@fcard) En este caso, debido al alcance léxico, a no está definido en el alcance de @M
por lo que utiliza la variable global ... En realidad, olvidé escapar de la expresión de flipplin en mi ejemplo tonto, pero el "solo funciona dentro del mismo módulo " parte de él todavía se aplica.
julia> module M macro m() :(a+1) end end M julia> a = 1 1 julia> M.@m ERROR: UndefVarError: a not defined
La razón es que, si la macro se utiliza en cualquier módulo distinto al que se definió en, todas las variables no definidas dentro del código a expandir se tratan como variables globales del módulo de la macro.
julia> macroexpand(:(M.@m)) :(M.a + 1)
AVANZADO
### @ Ismael-VC
@eval begin "do-until loop" macro $(:do)(block, until::Symbol, condition) until ≠ :until && error("@do expected `until` got `$until`") quote let $block @until $condition begin $block end end end |> esc end end julia> i = 0 0 julia> @do begin @show i i += 1 end until i == 5 i = 0 i = 1 i = 2 i = 3 i = 4
Macro de Scott:
"""
Internal function to return captured line number information from AST
##Parameters
- a: Expression in the julia type Expr
##Return
- Line number in the file where the calling macro was invoked
"""
_lin(a::Expr) = a.args[2].args[1].args[1]
"""
Internal function to return captured file name information from AST
##Parameters
- a: Expression in the julia type Expr
##Return
- The name of the file where the macro was invoked
"""
_fil(a::Expr) = string(a.args[2].args[1].args[2])
"""
Internal function to determine if a symbol is a status code or variable
"""
function _is_status(sym::Symbol)
sym in (:OK, :WARNING, :ERROR) && return true
str = string(sym)
length(str) > 4 && (str[1:4] == "ERR_" || str[1:5] == "WARN_" || str[1:5] == "INFO_")
end
"""
Internal function to return captured error code from AST
##Parameters
- a: Expression in the julia type Expr
##Return
- Error code from the captured info in the AST from the calling macro
"""
_err(a::Expr) =
(sym = a.args[2].args[2] ; _is_status(sym) ? Expr(:., :Status, QuoteNode(sym)) : sym)
"""
Internal function to produce a call to the log function based on the macro arguments and the AST from the ()->ERRCODE anonymous function definition used to capture error code, file name and line number where the macro is used
##Parameters
- level: Loglevel which has to be logged with macro
- a: Expression in the julia type Expr
- msgs: Optional message
##Return
- Statuscode
"""
function _log(level, a, msgs)
if isempty(msgs)
:( log($level, $(esc(:Symbol))($(_fil(a))), $(_lin(a)), $(_err(a)) )
else
:( log($level, $(esc(:Symbol))($(_fil(a))), $(_lin(a)), $(_err(a)), message=$(esc(msgs[1]))) )
end
end
macro warn(a, msgs...) ; _log(Warning, a, msgs) ; end
basura / sin procesar ...
ver / volcar una macro
(@ pi-) Supongamos que acabo de hacer macro m(); a+1; end
en un nuevo REPL. Sin a
definido. ¿Cómo puedo 'verlo'? como, ¿hay alguna manera de "volcar" una macro? Sin ejecutarlo realmente
(@fcard) Todo el código en macros se pone realmente en funciones, por lo que solo puede ver su código rebajado o de tipo inferido.
julia> macro m() a+1 end @m (macro with 1 method) julia> @code_typed @m LambdaInfo for @m() :(begin return Main.a + 1 end) julia> @code_lowered @m CodeInfo(:(begin nothing return Main.a + 1 end)) # ^ or: code_lowered(eval(Symbol("@m")))[1] # ouf!
Otras formas de obtener la función de una macro:
julia> macro getmacro(call) call.args[1] end @getmacro (macro with 1 method) julia> getmacro(name) = getfield(current_module(), name.args[1]) getmacro (generic function with 1 method) julia> @getmacro @m @m (macro with 1 method) julia> getmacro(:@m) @m (macro with 1 method)
julia> eval(Symbol("@M")) @M (macro with 1 method) julia> dump( eval(Symbol("@M")) ) @M (function of type #@M) julia> code_typed( eval(Symbol("@M")) ) 1-element Array{Any,1}: LambdaInfo for @M() julia> code_typed( eval(Symbol("@M")) )[1] LambdaInfo for @M() :(begin return $(Expr(:copyast, :($(QuoteNode(:(a + 1)))))) end::Expr) julia> @code_typed @M LambdaInfo for @M() :(begin return $(Expr(:copyast, :($(QuoteNode(:(a + 1)))))) end::Expr)
^ Parece que puedo usar code_typed
en code_typed
lugar
¿Cómo entender eval(Symbol("@M"))
?
(@fcard) Actualmente, cada macro tiene una función asociada. Si tiene una macro llamada M
, la función de la macro se llama @M
. En general, puede obtener el valor de una función con, por ejemplo, eval(:print)
pero con una función de macro, necesita hacer el Symbol("@M")
, ya que solo :@M
convierte en Expr(:macrocall, Symbol("@M"))
y la evaluación que provoca una macroexpansión.
¿Por qué code_typed
no muestra params?
(@Pi-)
julia> code_typed( x -> x^2 )[1] LambdaInfo for (::##5#6)(::Any) :(begin return x ^ 2 end)
^ Aquí veo uno ::Any
parámetro, pero no parece estar conectado con el token x
.
julia> code_typed( print )[1] LambdaInfo for print(::IO, ::Char) :(begin (Base.write)(io,c) return Base.nothing end::Void)
^ Similarmente aquí; no hay nada para conectar io
con el ::IO
Entonces, ¿no puede ser un volcado completo de la representación AST de ese método de print
particular ...?
(@fcard) print(::IO, ::Char)
solo le dice qué método es, no es parte del AST. Ya ni siquiera está presente en el maestro:
julia> code_typed(print)[1] CodeInfo(:(begin (Base.write)(io,c) return Base.nothing end))=>Void
(@ pi-) No entiendo lo que quieres decir con eso. Parece estar tirando el AST para el cuerpo de ese método, ¿no? Pensé que code_typed
le da el AST para una función. Pero parece que falta el primer paso, es decir, la configuración de tokens para params.
(@fcard) code_typed
está diseñado para mostrar solo el AST del cuerpo, pero por el momento proporciona el AST completo del método, en forma de LambdaInfo
(0.5) o CodeInfo
(0.6), pero se omite mucha información cuando se imprime a la respuesta. Deberá inspeccionar el campo LambdaInfo
por campo para obtener todos los detalles. dump
va a inundar su respuesta, por lo que podría intentar:
macro method_info(call) quote method = @code_typed $(esc(call)) print_info_fields(method) end end function print_info_fields(method) for field in fieldnames(typeof(method)) if isdefined(method, field) && !(field in [Symbol(""), :code]) println(" $field = ", getfield(method, field)) end end display(method) end print_info_fields(x::Pair) = print_info_fields(x[1])
Lo que da todos los valores de los campos nombrados de AST de un método:
julia> @method_info print(STDOUT, 'a') rettype = Void sparam_syms = svec() sparam_vals = svec() specTypes = Tuple{Base.#print,Base.TTY,Char} slottypes = Any[Base.#print,Base.TTY,Char] ssavaluetypes = Any[] slotnames = Any[Symbol("#self#"),:io,:c] slotflags = UInt8[0x00,0x00,0x00] def = print(io::IO, c::Char) at char.jl:45 nargs = 3 isva = false inferred = true pure = false inlineable = true inInference = false inCompile = false jlcall_api = 0 fptr = Ptr{Void} @0x00007f7a7e96ce10 LambdaInfo for print(::Base.TTY, ::Char) :(begin $(Expr(:invoke, LambdaInfo for write(::Base.TTY, ::Char), :(Base.write), :(io), :(c))) return Base.nothing end::Void)
¿Ver lil ' def = print(io::IO, c::Char)
? ¡Ahí tienes! (también los slotnames = [..., :io, :c]
part) También sí, la diferencia en la salida se debe a que estaba mostrando los resultados en el maestro.
???
(@ Ismael-VC) te refieres a esto? Despacho genérico con símbolos.
Puedes hacerlo de esta manera:
julia> function dispatchtest{alg}(::Type{Val{alg}}) println("This is the generic dispatch. The algorithm is $alg") end dispatchtest (generic function with 1 method) julia> dispatchtest(alg::Symbol) = dispatchtest(Val{alg}) dispatchtest (generic function with 2 methods) julia> function dispatchtest(::Type{Val{:Euler}}) println("This is for the Euler algorithm!") end dispatchtest (generic function with 3 methods) julia> dispatchtest(:Foo) This is the generic dispatch. The algorithm is Foo julia> dispatchtest(:Euler)
Esto es para el algoritmo de Euler! Me pregunto qué piensa @fcard sobre el envío de símbolos genéricos. --- ^: ángel:
Módulo Gotcha
@def m begin a+2 end @m # replaces the macro at compile-time with the expression a+2
Más exactamente, solo funciona dentro del nivel superior del módulo en el que se definió la macro.
julia> module M macro m1() a+1 end end M julia> macro m2() a+1 end @m2 (macro with 1 method) julia> a = 1 1 julia> M.@m1 ERROR: UndefVarError: a not defined julia> @m2 2 julia> let a = 20 @m2 end 2
esc
evita que esto suceda, pero la opción predeterminada de usarlo siempre va en contra del diseño del idioma. Una buena defensa para esto es evitar que uno use e introduzca nombres dentro de las macros, lo que los hace difíciles de rastrear a un lector humano.
Python `dict` / JSON como sintaxis para` Dict` literales.
Introducción
Julia usa la siguiente sintaxis para los diccionarios:
Dict({k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ)
Mientras Python y JSON se ven así:
{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}
Para fines ilustrativos también podríamos usar esta sintaxis en Julia y agregarle una nueva semántica (la sintaxis de Dict
es la forma idiomática en Julia, que se recomienda).
Primero veamos qué tipo de expresión es:
julia> parse("{1:2 , 3: 4}") |> Meta.show_sexpr
(:cell1d, (:(:), 1, 2), (:(:), 3, 4))
Esto significa que debemos tomar esta expresión :cell1d
y transformarla o devolver una nueva expresión que debería tener este aspecto:
julia> parse("Dict(1 => 2 , 3 => 4)") |> Meta.show_sexpr
(:call, :Dict, (:(=>), 1, 2), (:(=>), 3, 4))
Definición de macro
La siguiente macro, aunque simple, permite demostrar dicha generación y transformación de código:
macro dict(expr)
# Check the expression has the correct form:
if expr.head ≠ :cell1d || any(sub_expr.head ≠ :(:) for sub_expr ∈ expr.args)
error("syntax: expected `{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}`")
end
# Create empty `:Dict` expression which will be returned:
block = Expr(:call, :Dict) # :(Dict())
# Append `(key => value)` pairs to the block:
for pair in expr.args
k, v = pair.args
push!(block.args, :($k => $v))
end # :(Dict(k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ))
# Block is escaped so it can reach variables from it's calling scope:
return esc(block)
end
Echemos un vistazo a la macro expansión resultante:
julia> :(@dict {"a": :b, 'c': 1, :d: 2.0}) |> macroexpand
:(Dict("a" => :b,'c' => 1,:d => 2.0))
Uso
julia> @dict {"a": :b, 'c': 1, :d: 2.0}
Dict{Any,Any} with 3 entries:
"a" => :b
:d => 2.0
'c' => 1
julia> @dict {
"string": :b,
'c' : 1,
:symbol : π,
Function: print,
(1:10) : range(1, 10)
}
Dict{Any,Any} with 5 entries:
1:10 => 1:10
Function => print
"string" => :b
:symbol => π = 3.1415926535897...
'c' => 1
El último ejemplo es exactamente equivalente a:
Dict(
"string" => :b,
'c' => 1,
:symbol => π,
Function => print,
(1:10) => range(1, 10)
)
Mal uso
julia> @dict {"one": 1, "two": 2, "three": 3, "four": 4, "five" => 5}
syntax: expected `{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}`
julia> @dict ["one": 1, "two": 2, "three": 3, "four": 4, "five" => 5]
syntax: expected `{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}`
Tenga en cuenta que Julia tiene otros usos para los dos puntos :
como tal, tendrá que envolver las expresiones literales de rango entre paréntesis o usar la función de range
, por ejemplo.