Julia Language
Métaprogrammation
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ésPré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.