Recherche…


Syntaxe

  • nom de la macro (ex) ... fin
  • citation ... fin
  • : (...)
  • $ x
  • Meta.quot (x)
  • QuoteNode (x)
  • esc (x)

Remarques

Les fonctionnalités de métaprogrammation de Julia sont fortement inspirées de celles des langages de type Lisp et sembleront familières à ceux qui ont un fond Lisp. La métaprogrammation est très puissante. Utilisé correctement, il peut conduire à un code plus concis et lisible.

La quote ... end est la syntaxe quasiquote. Au lieu des expressions en cours d'évaluation, elles sont simplement analysées. La valeur de l'expression de quote ... end est l'arbre de syntaxe abstraite résultant (AST).

La syntaxe :(...) est similaire à la syntaxe quote ... end , mais elle est plus légère. Cette syntaxe est plus concise que la quote ... end .

À l’intérieur d’une quasiquote, l’opérateur $ est spécial et interpole son argument dans l’AST. L'argument devrait être une expression qui est directement raccordée à l'AST.

La fonction Meta.quot(x) cite son argument. Ceci est souvent utile en combinaison avec l'utilisation de $ pour l'interpolation, car cela permet d'épisser littéralement les expressions et les symboles dans l'AST.

Réimplémenter la macro @show

Dans Julia, la macro @show est souvent utile à des fins de débogage. Il affiche à la fois l'expression à évaluer et son résultat, renvoyant finalement la valeur du résultat:

julia> @show 1 + 1
1 + 1 = 2
2

Il est simple de créer notre propre version de @show :

julia> macro myshow(expression)
           quote
               value = $expression
               println($(Meta.quot(expression)), " = ", value)
               value
           end
       end

Pour utiliser la nouvelle version, utilisez simplement la macro @myshow :

julia> x = @myshow 1 + 1
1 + 1 = 2
2

julia> x
2

Jusqu'à la boucle

Nous sommes tous habitués à la syntaxe while , qui exécute son corps alors que la condition est évaluée à true . Que faire si nous voulons mettre en œuvre un until la boucle, qui exécute une boucle jusqu'à ce que la condition est évaluée à true ?

En Julia, nous pouvons le faire en créant une macro @until , qui cesse d’exécuter son corps lorsque la condition est remplie:

macro until(condition, expression)
    quote
        while !($condition)
            $expression
        end
    end |> esc
end

Nous avons utilisé ici la syntaxe de chaînage des fonctions |> , ce qui équivaut à appeler la fonction esc sur tout le bloc de quote . La fonction esc empêche l'application d'une macro hygiène au contenu de la macro. sans elle, les variables définies dans la macro seront renommées pour éviter les collisions avec des variables externes. Voir la documentation Julia sur la macro hygiène pour plus de détails.

Vous pouvez utiliser plus d'une expression dans cette boucle, en mettant simplement tout dans un bloc de 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 et Expr (: quote)

Il y a trois façons de citer quelque chose en utilisant une fonction Julia:

julia> QuoteNode(:x)
:(:x)

julia> Meta.quot(:x)
:(:x)

julia> Expr(:quote, :x)
:(:x)

Que signifie "citer", et à quoi cela sert-il? La citation nous permet de protéger les expressions contre l’interprétation de formes spéciales par Julia. Un cas d'utilisation courant est lorsque nous générons des expressions qui doivent contenir des éléments qui évaluent les symboles. (Par exemple, cette macro doit renvoyer une expression qui évalue un symbole.) Cela ne fonctionne pas simplement pour renvoyer le symbole:

julia> macro mysym(); :x; end
@mysym (macro with 1 method)

julia> @mysym
ERROR: UndefVarError: x not defined

julia> macroexpand(:(@mysym))
:x

Que se passe t-il ici? @mysym développe en :x , qui en tant qu'expression devient la variable x . Mais rien n'a encore été attribué à x , nous obtenons donc une erreur x not defined .

Pour contourner ce problème, nous devons citer le résultat de notre macro:

julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)

julia> @mysym2
:x

julia> macroexpand(:(@mysym2))
:(:x)

Ici, nous avons utilisé la fonction Meta.quot pour transformer notre symbole en un symbole entre guillemets, ce qui est le résultat souhaité.

Quelle est la différence entre Meta.quot et QuoteNode , et que dois-je utiliser? Dans presque tous les cas, la différence importe peu. Il est peut-être un peu plus sûr parfois d'utiliser QuoteNode au lieu de Meta.quot . Explorer la différence est instructif sur le fonctionnement des expressions et des macros Julia.

La différence entre Meta.quot et QuoteNode , expliquée

Voici une règle générale:

  • Si vous avez besoin ou souhaitez prendre en charge l'interpolation, utilisez Meta.quot ;
  • Si vous ne pouvez pas ou ne voulez pas autoriser l'interpolation, utilisez QuoteNode .

En bref, la différence est que Meta.quot permet l'interpolation dans la chose citée, tandis que QuoteNode protège son argument de toute interpolation. Pour comprendre l'interpolation, il est important de mentionner l'expression $ . Il y a une sorte d'expression dans Julia appelée $ expression. Ces expressions permettent de s'échapper. Par exemple, considérons l'expression suivante:

julia> ex = :( x = 1; :($x + $x) )
quote 
    x = 1
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

Lorsqu'elle est évaluée, cette expression évalue 1 et l'assigne à x , puis construit une expression de la forme _ + _ où le _ sera remplacé par la valeur de x . Ainsi, le résultat devrait être l' expression 1 + 1 (qui n'est pas encore évaluée, et donc distincte de la valeur 2 ). En effet, c'est le cas:

julia> eval(ex)
:(1 + 1)

Disons maintenant que nous écrivons une macro pour construire ces types d'expressions. Notre macro prendra un argument qui remplacera le 1 dans l' ex ci-dessus. Cet argument peut être n'importe quelle expression, bien sûr. Voici quelque chose qui n'est pas tout à fait ce que nous voulons:

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

Le deuxième cas est incorrect, car nous devrions garder 1 + 1 évalué. Nous corrigeons cela en citant l'argument avec 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 macro hygiène ne s’applique pas au contenu d’un devis, donc s’échapper n’est pas nécessaire dans ce cas (et en fait pas légal) dans ce cas.

Comme mentionné précédemment, Meta.quot permet l'interpolation. Alors essayons ça:

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

À partir du premier exemple, nous voyons que l’interpolation nous permet d’inclure le sin(1) , au lieu d’avoir l’expression comme un sin(1) littéral sin(1) . Le second exemple montre que cette interpolation est effectuée dans la portée de l'invocation de la macro, et non dans la portée de la macro. C'est parce que notre macro n'a pas réellement évalué de code; tout ce qu'il fait génère du code. L'évaluation du code (qui fait son chemin dans l'expression) est effectuée lorsque l'expression générée par la macro est réellement exécutée.

Et si on avait utilisé QuoteNode place? Comme vous pouvez le deviner, puisque QuoteNode empêche l'interpolation, cela ne fonctionnera pas.

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

Dans cet exemple, nous pourrions convenir que Meta.quot offre une plus grande flexibilité, car il permet une interpolation. Alors, pourquoi pourrions-nous jamais envisager d'utiliser QuoteNode ? Dans certains cas, nous ne souhaitons peut-être pas réellement une interpolation et souhaitons en fait l'expression littérale $ . Quand cela serait-il souhaitable? Considérons une généralisation de @makeex où nous pouvons passer des arguments supplémentaires déterminant ce qui se passe à gauche et à droite du signe + :

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)

Une limitation de notre implémentation de @makeex4 est que nous ne pouvons pas utiliser directement les expressions comme côtés droit et gauche de l'expression, car elles sont interpolées. En d'autres termes, les expressions peuvent être évaluées pour l'interpolation, mais nous pourrions vouloir les conserver. (Comme il y a plusieurs niveaux de cotation et d'évaluation ici, clarifions: notre macro génère du code qui construit une expression qui, lorsqu'elle est évaluée, produit une autre expression . Ouf!)

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)

Nous devrions permettre à l'utilisateur de spécifier quand l'interpolation doit avoir lieu, et quand elle ne devrait pas. Théoriquement, c'est une solution facile: nous pouvons simplement supprimer l'un des signes $ dans notre application et laisser l'utilisateur contribuer. Cela signifie que nous interpolons une version citée de l'expression saisie par l'utilisateur (que nous avons déjà citée et interpolée une fois). Cela conduit au code suivant, qui peut être un peu déroutant au début, en raison des multiples niveaux imbriqués de cotation et de non-classement. Essayez de lire et de comprendre à quoi sert chaque évasion.

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

Les choses ont bien commencé, mais quelque chose a mal tourné. Le code généré par la macro tente d'interpoler la copie de y dans la portée de l'invocation de la macro. mais il n'y a pas de copie de y dans la portée de l'invocation de macro. Notre erreur permet l'interpolation avec les deuxième et troisième arguments de la macro. Pour corriger cette erreur, nous devons utiliser 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)

En utilisant QuoteNode , nous avons protégé nos arguments de l'interpolation. Comme QuoteNode n'a pour effet que des protections supplémentaires, il n'est jamais nuisible d'utiliser QuoteNode , sauf si vous souhaitez une interpolation. Cependant, comprendre la différence permet de comprendre où et pourquoi Meta.quot pourrait être un meilleur choix.

Ce long exercice est un exemple qui est clairement trop complexe pour apparaître dans une application raisonnable. Par conséquent, nous avons justifié la règle suivante, mentionnée précédemment:

  • Si vous avez besoin ou souhaitez prendre en charge l'interpolation, utilisez Meta.quot ;
  • Si vous ne pouvez pas ou ne voulez pas autoriser l'interpolation, utilisez QuoteNode .

Qu'en est-il d'Expr (: citation)?

Expr(:quote, x) est équivalent à Meta.quot(x) . Cependant, ce dernier est plus idiomatique et est préféré. Pour le code qui utilise beaucoup la métaprogrammation, une ligne using Base.Meta est souvent utilisée, ce qui permet de Meta.quot simplement quot

Guider

Les bits et bobs de métaprogrammation de π

Buts:

  • Enseigner à l'aide d'exemples fonctionnels / utiles / non abstraits @swap (par exemple, @swap ou @assert ) qui introduisent des concepts dans des contextes appropriés

  • Préfère laisser le code illustrer / démontrer les concepts plutôt que des paragraphes d'explication

  • Évitez de lier la «lecture requise» aux autres pages - cela interrompt la narration

  • Présenter les choses dans un ordre raisonnable qui rendra l'apprentissage plus facile

Ressources:

julialang.org
wikibook (@Cormullion)
5 couches (Leah Hanson)
SO-Doc Quoting (@TotalVerb)
SO-Doc - Symboles qui ne sont pas des identifiants légaux (@TotalVerb)
SO: Qu'est-ce qu'un symbole dans Julia (@StefanKarpinski)
Fil de discussion (@ pi-) Métaprogrammation

La majeure partie du matériel provient de la chaîne discursive, la plus grande partie provient de fcard ... s'il vous plaît, pousse-moi si j'avais des attributions oubliées.

symbole

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

Passer des drapeaux en fonctions:

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 exemple de hashkey:

number_names = Dict{Symbol, Int}()
number_names[:one] = 1
number_names[:two] = 2
number_names[:six] = 6

(Advanced) (@fcard) :foo aka :(foo) donne un symbole si foo est un identifiant valide, sinon une expression.

# 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
...

Fondamentalement, vous pouvez traiter les symboles comme des chaînes légères. Ce n'est pas ce qu'ils sont pour, mais vous pouvez le faire, alors pourquoi pas. La base de Julia elle-même le fait, print_with_color(:red, "abc") imprime un abc de couleur rouge.

Expr (AST)

(Presque) tout dans Julia est une expression, c’est-à-dire une instance de Expr , qui contiendra 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

Imbrication 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 s utilisant 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

... donc quote est fonctionnellement le même mais fournit des informations de débogage supplémentaires.

(*) ASTUCE : utilisez let pour garder x dans le bloc

quote un quote

Expr(:quote, x) est utilisé pour représenter des guillemets entre guillemets.

Expr(:quote, :(x + y)) == :(:(x + y))

Expr(:quote, Expr(:$, :x)) == :(:($x))

QuoteNode(x) est similaire à Expr(:quote, x) mais empêche l'interpolation.

eval(Expr(:quote, Expr(:$, 1))) == 1

eval(QuoteNode(Expr(:$, 1))) == Expr(:$, 1)

( Désambiguïser les différents mécanismes de citation dans la métaprogrammation de Julia

Est-ce que $ et : (…) sont en quelque sorte inversés?

:(foo) signifie "ne regarde pas la valeur, regarde l'expression" $foo signifie "change l'expression à sa valeur"

:($(foo)) == foo . $(:(foo)) est une erreur. $(...) n'est pas une opération et ne fait rien par lui-même, c'est un "interpoler ça!" signe que la syntaxe de citation utilise. C'est-à-dire qu'il n'existe que dans une citation.

Est-ce que $ foo le même que eval( foo ) ?

Non! $foo est échangé pour la valeur de compilation eval(foo) signifie le faire à l'exécution

eval se produira dans l'interpolation de portée globale locale

eval(:<expr>) doit renvoyer la même chose que simplement <expr> (en supposant que <expr> soit une expression valide dans l'espace global actuel)

eval(:(1 + 2)) == 1 + 2

eval(:(let x=1; x + 1 end)) == let x=1; x + 1 end

macro s

Prêt? :)

# let's try to make this!
julia> x = 5; @show x;
x = 5

Faisons notre propre 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 pour baisser un Expr

5 couches (Leah Hanson) <- explique comment Julia prend le code source comme une chaîne, il tokenizes dans un Expr -tree (AST), étend toutes les macros (encore AST), réduit (réduit AST), convertit ensuite en LLVM (et au-delà - en ce moment, nous n'avons pas besoin de nous inquiéter de ce qui se trouve au-delà!)

Q: code_lowered agit sur les fonctions. Est-il possible d'abaisser un Expr ? A: yup!

# 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 vous souhaitez uniquement développer des macros, vous pouvez utiliser 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

... qui renvoie un AST non abaissé mais avec toutes les macros développées.

esc()

esc(x) renvoie un Expr qui dit "ne pas appliquer d'hygiène à ceci", c'est la même chose Expr(:escape, x) . L'hygiène est ce qui garde une macro autonome, et vous esc choses si vous voulez qu'elles fuient. par exemple

Exemple: swap macro pour illustrer 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

$ nous permet d'échapper à la quote . Alors pourquoi ne pas simplement $p et $q ? c'est à dire

    # FAIL!
    tmp = $p
    $p = $q
    $q = tmp

Parce que cela regarderait d'abord la portée de macro pour p , et trouverait un p local, c'est-à-dire le paramètre p (oui, si vous accédez ensuite à p sans esc , la macro considère le paramètre p comme une variable locale).

Donc, $p = ... est juste une assignation au p local. cela n'affecte pas la variable transmise dans le contexte d'appel.

Ok, alors qu'en est-il de:

    # Almost!
    tmp = $p          # <-- you might think we don't 
    $(esc(p)) = $q    #       need to esc() the RHS
    $(esc(q)) = tmp

Donc , esc(p) est « » fuite p dans le contexte d'appel. "La chose qui a été passée dans la macro que nous recevons comme 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                                      

Comme vous pouvez le voir, tmp obtient le traitement d'hygiène #10#tmp , alors que x et y ne le font pas. Julia gensym un identifiant unique pour tmp , ce que vous pouvez faire manuellement avec gensym , à savoir:

julia> gensym(:tmp)
Symbol("##tmp#270")

Mais: il y a 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

Une autre chose que fait la macro hygiène de Julia, c'est que si la macro provient d'un autre module, les variables (qui n'ont pas été assignées dans la macro, comme tmp dans ce cas) sont globales au module actuel, donc $p devient Swap.$p , de même $q -> Swap.$q .

En général, si vous avez besoin d'une variable en dehors de la portée de la macro, vous devriez l'écouter. Vous devriez donc vous esc(p) et esc(q) qu'elles se trouvent sur le LHS ou le RHS d'une expression, voire sur elles-mêmes.

les gens ont déjà mentionné quelques fois le gensym sa et bientôt vous serez séduit par le côté sombre du défaut d'échapper à toute l'expression avec quelques gensym ici et là, mais ... Assurez-vous de comprendre comment fonctionne l'hygiène avant d'essayer d'être plus intelligent que ça! Ce n'est pas un algorithme particulièrement complexe, donc cela ne devrait pas prendre trop de temps, mais ne vous précipitez pas! N'utilisez pas ce pouvoir tant que vous n'en avez pas compris toutes les ramifications ... (@fcard)

Exemple: 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) |> est controversé, cependant. Je suis surpris qu'une foule n'ait pas encore discuté. (peut-être que tout le monde en a assez). Il y a une recommandation d'avoir la plupart sinon la totalité de la macro soit juste un appel à une fonction, donc:

macro until(condition, block)
    esc(until(condition, block))
end

function until(condition, block)
    quote
        while !$condition
            $block
        end
    end
end

... est une alternative plus sûre.

Le défi macro simple de ## @ fcard

Tâche: permuter les opérandes, donc swaps(1/2) donne 2.00 ie 2/1

macro swaps(e)
    e.args[2:3] = e.args[3:-1:2]   
    e
end
@swaps(1/2)
2.00

Plus de défis macro à partir de @fcard ici


Interpolation et 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: Pourquoi le dernier $ ? R: Il interpole, c’est-à-dire qu’il force Julia à eval cette string(ex) lorsque l’exécution passe par l’invocation de cette macro. c.-à-d. si vous exécutez simplement ce code, cela ne forcera aucune évaluation. Mais dès que vous assert(foo) Julia invoquera cette macro en remplaçant son jeton / expr AST par ce qu'elle renvoie, et le $ mettra en action.

Un hack amusant pour utiliser {} pour les blocs

(@fcard) Je ne pense pas qu'il y ait quelque chose de technique empêchant {} d'être utilisé comme des blocs, en fait on peut même caler la syntaxe résiduelle {} pour la faire fonctionner:

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

* (peu susceptible de continuer si / quand la syntaxe {} est réutilisée)


Donc, d'abord, Julia voit le jeton de macro, donc il va lire / analyser les jetons jusqu'à la end correspondante, et créer quoi? Un Expr avec .head=:macro ou quelque chose? Stocke-t-il "a+1" tant que chaîne ou le sépare-t-il en :+(:a, 1) ? Comment voir?

?

(@fcard) Dans ce cas, à cause de la portée lexicale, a n'est pas défini dans la @M de @M , il utilise donc la variable globale ... J'ai en fait oublié d'échapper à l'expression flipplin 'dans mon exemple idiot, mais la même module "une partie de cela s'applique toujours.

julia> module M
       macro m()
         :(a+1)
       end
       end
M

julia> a = 1
1

julia> M.@m
ERROR: UndefVarError: a not defined

La raison en est que, si la macro est utilisée dans un module autre que celui dans lequel elle a été définie, toutes les variables non définies dans le code à développer sont traitées comme des globales du module de la macro.

julia> macroexpand(:(M.@m))
:(M.a + 1)

AVANCÉE

### @ 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

La 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

junk / non traité ...

afficher / vider une macro

(@ pi-) Supposons que je fasse juste la macro m(); a+1; end dans un nouveau REPL. Sans a définition. Comment puis-je le voir? Par exemple, existe-t-il un moyen de "vider" une macro? Sans l'exécuter réellement

(@fcard) Tout le code dans les macros est effectivement mis en fonctions, de sorte que vous ne pouvez voir que leur code abaissé ou inféré.

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!

Autres moyens d'obtenir la fonction d'une 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)

^ semble que je puisse utiliser code_typed place

Comment comprendre eval(Symbol("@M")) ?

(@fcard) Actuellement, chaque macro est associée à une fonction. Si vous avez une macro appelée M , la fonction de la macro s'appelle @M . En général, vous pouvez obtenir une valeur de fonction avec par exemple eval(:print) mais avec une fonction de macro, vous devez faire Symbol("@M") , car juste :@M devient un Expr(:macrocall, Symbol("@M")) et l’évaluation qui provoque une macro-expansion.

Pourquoi ne pas afficher les paramètres de code_typed ?

(@pi-)

julia> code_typed( x -> x^2 )[1]
LambdaInfo for (::##5#6)(::Any)
:(begin 
        return x ^ 2
    end)

^ ici je vois un ::Any paramètre, mais il ne semble pas être connecté avec le jeton x .

 julia> code_typed( print )[1]
LambdaInfo for print(::IO, ::Char)
:(begin 
        (Base.write)(io,c)
        return Base.nothing
    end::Void)

^ pareil ici; il n'y a rien de se connecter io avec le ::IO donc sûrement cela ne peut pas être une décharge complète de la représentation AST de cette particulière print méthode ...?

(@fcard) print(::IO, ::Char) ne vous dit que la méthode utilisée, elle ne fait pas partie de l'AST. Il n'est même plus présent dans master:

julia> code_typed(print)[1]
CodeInfo(:(begin
        (Base.write)(io,c)
        return Base.nothing
    end))=>Void

(@ pi-) Je ne comprends pas ce que tu veux dire par là. Il semble que le dumping de l'AST pour le corps de cette méthode, non? Je pensais que code_typed donne l'AST pour une fonction. Mais il semble manquer la première étape, à savoir la mise en place de jetons pour les paramètres.

(@fcard) code_typed est censé afficher uniquement l'AST du corps, mais pour l'instant il donne l'AST complète de la méthode, sous la forme d'un LambdaInfo (0.5) ou CodeInfo (0.6), mais beaucoup d'informations sont omises lorsqu'il est imprimé sur le repl. Vous devrez inspecter le champ LambdaInfo par champ pour obtenir tous les détails. dump va inonder votre réplique, vous pouvez donc essayer:

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])

Ce qui donne toutes les valeurs des champs nommés de l'AST d'une méthode:

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)

Voir le lil ' def = print(io::IO, c::Char) ? Voilà! (aussi les slotnames = [..., :io, :c] part) Aussi oui, la différence de sortie est que je montrais les résultats sur master.

???

(@ Ismael-VC) tu veux dire comme ça? Envoi générique avec symboles

Vous pouvez le faire de cette façon:

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)

C'est pour l'algorithme d'Euler! Je me demande ce que @fcard pense de la répartition des symboles génériques! --- ^: ange:

Module Gotcha

@def m begin
  a+2
end

@m # replaces the macro at compile-time with the expression a+2

Plus précisément, ne fonctionne que dans le niveau supérieur du module dans lequel la macro a été définie.

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 empêche que cela se produise, mais le fait de ne jamais l'utiliser systématiquement va à l'encontre de la conception du langage. Une bonne défense consiste à ne pas utiliser et introduire de noms dans les macros, ce qui les rend difficiles à suivre pour un lecteur humain.

Python `dict` / JSON comme syntaxe pour les littéraux` Dict`.

introduction

Julia utilise la syntaxe suivante pour les dictionnaires:

Dict({k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ)

Alors que Python et JSON ressemblent à ceci:

{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}

A des fins d'illustration, nous pourrions également utiliser cette syntaxe dans Julia et y ajouter de nouvelles sémantiques (la syntaxe Dict est la méthode idiomatique dans Julia, qui est recommandée).

Voyons d'abord quel type d'expression il s'agit:

julia> parse("{1:2 , 3: 4}") |> Meta.show_sexpr
(:cell1d, (:(:), 1, 2), (:(:), 3, 4))

Cela signifie que nous devons prendre l'expression :cell1d et la transformer ou renvoyer une nouvelle expression qui devrait ressembler à ceci:

julia> parse("Dict(1 => 2 , 3 => 4)") |> Meta.show_sexpr
(:call, :Dict, (:(=>), 1, 2), (:(=>), 3, 4))

Définition de macro

La macro suivante, bien que simple, permet de démontrer une telle génération et transformation de code:

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

Jetons un coup d'œil à l'expansion de la macro qui en résulte:

julia> :(@dict {"a": :b, 'c': 1, :d: 2.0}) |> macroexpand
:(Dict("a" => :b,'c' => 1,:d => 2.0))

Usage

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         

Le dernier exemple est exactement équivalent à:

Dict(                      
    "string" => :b,            
    'c'      => 1,             
    :symbol  => π,             
    Function => print,         
    (1:10)   => range(1, 10)   
)

Abus

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ₙ}`

Notez que Julia a d'autres utilisations pour les deux : points : vous devrez donc envelopper les expressions littérales avec des parenthèses ou utiliser la fonction de range , par exemple.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow