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,@swapou@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.