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 adecuados

  • Prefiero 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.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow