Julia Language
Метапрограммирование
Поиск…
Синтаксис
- имя макроса (ex) ... end
- цитата ... конец
- : (...)
- $ х
- Meta.quot (х)
- QuoteNode (х)
- ESC (х)
замечания
Функции метапрограммирования Джулии в значительной степени вдохновлены теми же Lisp-подобными языками и будут казаться знакомыми тем, у кого есть фон Лиспа. Метапрограммирование очень мощное. При правильном использовании это может привести к получению более сжатого и читаемого кода.
quote ... end
- синтаксис квазикота. Вместо выражений внутри оценки они просто анализируются. Значение выражения quote ... end
- это результирующее синтаксическое дерево (AST).
Синтаксис :(...)
аналогичен синтаксису quote ... end
, но он более легкий. Этот синтаксис более краток, чем quote ... end
.
Внутри квазиквадрата оператор $
является специальным и интерполирует его аргумент в АСТ. Ожидается, что аргумент будет выражением, которое сплайсируется непосредственно в AST.
Функция Meta.quot(x)
цитирует свой аргумент. Это часто полезно в сочетании с использованием $
for интерполяции, так как позволяет выражениям и символам спланировать буквально в AST.
Повторное выполнение макроса @show
В Julia макрос @show
часто полезен для целей отладки. Он отображает как выражение, подлежащее оценке, так и его результат, и, наконец, возвращает значение результата:
julia> @show 1 + 1
1 + 1 = 2
2
Прямо создать собственную версию @show
:
julia> macro myshow(expression)
quote
value = $expression
println($(Meta.quot(expression)), " = ", value)
value
end
end
Чтобы использовать новую версию, просто используйте макрос @myshow
:
julia> x = @myshow 1 + 1 1 + 1 = 2 2 julia> x 2
До цикла
Мы все привыкли к синтаксису while
, который выполняет свое тело, а условие оценивается как true
. Что делать , если мы хотим реализовать until
цикла, который выполняет цикл , пока условие оценивается в true
?
В Julia мы можем сделать это, создав макрос @until
, который перестает выполнять свое тело при выполнении условия:
macro until(condition, expression) quote while !($condition) $expression end end |> esc end
Здесь мы использовали синтаксис цепочки функций |>
, который эквивалентен вызову функции esc
во всем кадре quote
. Функция esc
предотвращает применение макрогиды к содержимому макроса; без него переменные, скопированные в макрос, будут переименованы для предотвращения столкновений с внешними переменными. Для получения дополнительной информации см. Документацию Julia о макрогигалии .
Вы можете использовать более одного выражения в этом цикле, просто поместив все в begin ... end
block:
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 и Expr (: quote)
Есть три способа процитировать что-то, используя функцию Julia:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
Что означает «цитирование», и для чего это полезно? Цитирование позволяет нам защищать выражения от интерпретации Джулией специальных форм. Обычный пример использования - это когда мы генерируем выражения, которые должны содержать вещи, которые оцениваются символами. (Например, этот макрос должен вернуть выражение, которое оценивает символ.) Это не работает просто для возврата символа:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
Что тут происходит? @mysym
расширяет до :x
, который как выражение интерпретируется как переменная x
. Но ничто не было назначено для x
еще, поэтому мы получаем x not defined
ошибку.
Чтобы обойти это, мы должны привести результат нашего макроса:
julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)
julia> @mysym2
:x
julia> macroexpand(:(@mysym2))
:(:x)
Здесь мы использовали функцию Meta.quot
чтобы превратить наш символ в Meta.quot
символ, который мы хотим.
В чем разница между Meta.quot
и QuoteNode
, и какой я должен использовать? Почти во всех случаях разница не имеет большого значения. Иногда, возможно, немного безопаснее использовать QuoteNode
вместо Meta.quot
. Однако изучение различий является информативным в том, как работают выражения Julia и макросы.
Разница между Meta.quot
и QuoteNode
, объясняется
Вот эмпирическое правило:
- Если вам нужно или хотите поддерживать интерполяцию, используйте
Meta.quot
; - Если вы не можете или не хотите разрешать интерполяцию, используйте
QuoteNode
.
Короче говоря, разница в том, что Meta.quot
допускает интерполяцию в цитируемой QuoteNode
, а QuoteNode
защищает ее аргумент от любой интерполяции. Чтобы понять интерполяцию, важно упомянуть выражение $
. В Джулии есть выражение, называемое выражением $
. Эти выражения позволяют ускользнуть. Например, рассмотрим следующее выражение:
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
При оценке это выражение будет оценивать 1
и назначать его x
, а затем строить выражение вида _ + _
где _
будет заменено значением x
. Таким образом, результатом этого должно быть выражение 1 + 1
(которое еще не оценено и поэтому отличается от значения 2
). Действительно, это так:
julia> eval(ex)
:(1 + 1)
Скажем, теперь мы пишем макрос для создания таких выражений. Наш макрос примет аргумент, который заменит 1
в ex
выше. Разумеется, этот аргумент может быть любым выражением. Вот что-то, чего мы не хотим:
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
Второй случай неверен, потому что мы должны оставить 1 + 1
необоснованным. Мы исправим это, указав аргумент с 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
Макро-гигиена не относится к содержанию цитаты, поэтому в этом случае экранирование не обязательно (и на самом деле не является законным).
Как упоминалось ранее, Meta.quot
позволяет интерполяцию. Итак, давайте попробуем:
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
Из первого примера мы видим, что интерполяция позволяет нам встраивать sin(1)
вместо того, чтобы выражение было буквальным sin(1)
. Второй пример показывает, что эта интерполяция выполняется в области подсчета макросов, а не в собственной области макросов. Это потому, что наш макрос фактически не оценил какой-либо код; все, что он делает, это генерировать код. Оценка кода (который делает его путь в выражении) выполняется, когда выражение, которое генерирует макрос, фактически выполняется.
Что, если бы мы вместо этого использовали QuoteNode
? Как вы можете догадаться, поскольку QuoteNode
предотвращает интерполяцию вообще, это означает, что это не сработает.
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
В этом примере мы можем согласиться с тем, что Meta.quot
дает большую гибкость, поскольку позволяет интерполяцию. Так почему мы можем когда-нибудь рассмотреть использование QuoteNode
? В некоторых случаях мы не можем на самом деле желать интерполяции и на самом деле хотим выражение буквального $
. Когда это было бы желательно? Рассмотрим обобщение @makeex
где мы можем передать дополнительные аргументы, определяющие, что происходит слева и справа от знака +
:
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)
Ограничением нашей реализации @makeex4
является то, что мы не можем использовать выражения как левую и правую стороны выражения напрямую, потому что они интерполируются. Другими словами, выражения могут быть оценены для интерполяции, но мы можем захотеть их сохранить. (Поскольку здесь имеется много уровней цитирования и оценки, давайте выясним: наш макрос генерирует код, который строит выражение, которое при оценке производит другое выражение . Фу!)
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)
Мы должны разрешить пользователю указывать, когда произойдет интерполяция, и когда это не должно. Теоретически это легко исправить: мы можем просто удалить один из знаков $
в нашем приложении и позволить пользователю внести свой вклад. Это означает, что мы интерполируем цитированную версию выражения, введенного пользователем (которое мы уже цитировали и интерполировали один раз). Это приводит к следующему коду, который может быть немного запутанным вначале из-за множественных вложенных уровней цитирования и нечеткости. Попытайтесь прочитать и понять, для чего каждый побег.
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
Все началось хорошо, но что-то пошло не так. Созданный макросом код пытается интерполировать копию y
в области вызова макроса; но нет копии y
в области подсчета макросов. Наша ошибка позволяет интерполяцию со вторым и третьим аргументами в макросе. Чтобы исправить эту ошибку, мы должны использовать 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)
Используя QuoteNode
, мы защитили наши аргументы от интерполяции. Поскольку QuoteNode
обладает только дополнительными защитами, никогда не вредно использовать QuoteNode
, если вы не хотите интерполяции. Однако понимание разницы позволяет понять, где и почему Meta.quot
может быть лучшим выбором.
Это длительное упражнение представляет собой пример, который слишком сложный, чтобы проявляться в любом разумном приложении. Поэтому мы обосновали следующее эмпирическое правило, упомянутое ранее:
- Если вам нужно или хотите поддерживать интерполяцию, используйте
Meta.quot
; - Если вы не можете или не хотите разрешать интерполяцию, используйте
QuoteNode
.
Что относительно Expr (: quote)?
Expr(:quote, x)
эквивалентен Meta.quot(x)
. Однако последний более идиоматичен и является предпочтительным. Для кода, который сильно использует метапрограммирование, часто используется линия using Base.Meta
, которая позволяет Meta.quot
упоминаться как просто quot
.
Руководство
Метапрограммирование метаданных & bobs
Цели:
Учите минимальные целевые функциональные / полезные / не абстрактные примеры (например,
@swap
или@assert
), которые вводят концепции в подходящих контекстахПредпочитаете, чтобы код иллюстрировал / демонстрировал концепции, а не параграфы объяснения
Избегайте связывания «требуемого чтения» с другими страницами - это прерывает повествование
Представьте вещи в разумном порядке, которые облегчат обучение
Ресурсы:
julialang.org
wikibook (@Cormullion)
5 слоев (Leah Hanson)
Котировка SO-Doc (@TotalVerb)
SO-Doc - Символы, которые не являются юридическими идентификаторами (@TotalVerb)
SO: Что такое символ в Джулии (@StefanKarpinski)
Дискурсная нить (@ pi-) Метапрограммирование
Большая часть материала пришла из канала дискурса, большая часть которого исходит от fcard ... пожалуйста, подтачивайте меня, если я забыл атрибуты.
Условное обозначение
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
Передача флагов в функции:
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
Пример hashkey:
number_names = Dict{Symbol, Int}() number_names[:one] = 1 number_names[:two] = 2 number_names[:six] = 6
(Дополнительно) (@fcard) :foo
aka :(foo)
дает символ, если foo
является допустимым идентификатором, иначе выражение.
# 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 ...
В основном вы можете рассматривать Символы как легкие строки. Это не то, за что они предназначены, но вы можете это сделать, так почему бы и нет. Сама Julia's Base делает это, print_with_color(:red, "abc")
печатает красную абстракцию.
Expr (AST)
(Почти) все в Юлии - это выражение, то есть экземпляр Expr
, который будет содержать 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
Expr
:
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 с использованием 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
... так что quote
функционально одинакова, но предоставляет дополнительную информацию об отладке.
(*) СОВЕТ: Используйте let
держать x
внутри блока
quote
из quote
Expr(:quote, x)
используется для представления котировок в кавычках.
Expr(:quote, :(x + y)) == :(:(x + y))
Expr(:quote, Expr(:$, :x)) == :(:($x))
QuoteNode(x)
похож на Expr(:quote, x)
но он предотвращает интерполяцию.
eval(Expr(:quote, Expr(:$, 1))) == 1
eval(QuoteNode(Expr(:$, 1))) == Expr(:$, 1)
( Разберитесь с различными механизмами цитирования в метапрограмме Джулии
Существуют ли $ и : (...) как-то обратные друг от друга?
:(foo)
означает «не смотрите на значение, посмотрите на выражение« $foo
означает «изменить выражение на его значение»
:($(foo)) == foo
. $(:(foo))
является ошибкой. $(...)
не является операцией и ничего не делает сам по себе, это «интерполировать это!». что используется синтаксис цитирования. т.е. он существует только внутри цитаты.
Является ли $
foo
таким же, как eval(
foo
)
?
Нет! $foo
обменивается на значение времени compile-времени eval(foo)
означает сделать это во время выполнения
eval
будет происходить в глобальной интерполяции.
eval(:<expr>)
должен возвращать то же самое, что просто <expr>
(предполагая, что <expr>
является допустимым выражением в текущем глобальном пространстве)
eval(:(1 + 2)) == 1 + 2
eval(:(let x=1; x + 1 end)) == let x=1; x + 1 end
macro
Готовы? :)
# let's try to make this! julia> x = 5; @show x; x = 5
Давайте сделаем наш собственный макрос @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
чтобы уменьшить Expr
5 слоев (Leah Hanson) <- объясняет, как Джулия берет исходный код в виде строки, маркирует его в Expr
(AST), расширяет все макросы (все еще AST), понижает (понижает AST), затем преобразует в LLVM (и дальше - на данный момент нам не нужно беспокоиться о том, что лежит за пределами!)
Q: code_lowered
действует на функции. Можно ли снизить 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)
Если вы хотите только развернуть макросы, вы можете использовать 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
... который возвращает не пониженный АСТ, но со всеми макросами.
esc()
esc(x)
возвращает Expr, в котором говорится: «Не применяйте к нему гигиенические Expr(:escape, x)
», это то же самое, что и Expr(:escape, x)
. Гигиена , что держит макрос самодостаточным, и вы esc
вещи , если вы хотите, чтобы «утечки». например
Пример: макрос swap
для иллюстрации 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
$
позволяет нам «убежать от» quote
. Так почему бы не просто $p
и $q
? т.е.
# FAIL! tmp = $p $p = $q $q = tmp
Поскольку это будет выглядеть сначала в области macro
для p
, и он найдет локальный p
то есть параметр p
(да, если вы впоследствии получите доступ к p
без esc
ing, макрос считает параметр p
в качестве локальной переменной).
Таким образом, $p = ...
- это просто присвоение локальному p
. это не влияет на передачу какой-либо переменной в вызывающий контекст.
Хорошо, а как насчет:
# Almost! tmp = $p # <-- you might think we don't $(esc(p)) = $q # need to esc() the RHS $(esc(q)) = tmp
Таким образом, esc(p)
«утечка» p
в вызывающий контекст. «То, что было передано в макрос, который мы получаем как 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
Как вы можете видеть, tmp
получает гигиеническую терапию #10#tmp
, тогда как x
и y
нет. Джулия делает уникальный идентификатор для tmp
, что вы можете вручную сделать с gensym
, то есть:
julia> gensym(:tmp) Symbol("##tmp#270")
Но: есть 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
Другое дело, что макро-гигиена julia заключается в том, что если макрос находится из другого модуля, он делает любые переменные (которые не были назначены внутри возвращаемого выражения макроса, например tmp
в этом случае), глобальные значения текущего модуля, поэтому $p
становится Swap.$p
, также $q
-> Swap.$q
.
В общем случае, если вам нужна переменная, которая находится вне области макроса, вы должны ее использовать, поэтому вы должны использовать esc(p)
и esc(q)
независимо от того, находятся ли они в LHS или RHS выражения или даже сами по себе.
люди уже упоминали gensym
sa несколько раз, и вскоре вас соблазнит темная сторона дефолта, чтобы избежать всего выражения с несколькими gensym
s peppered здесь и там, но ... Удостоверьтесь, чтобы понять, как работает гигиена, прежде чем пытаться быть умнее этого! Это не очень сложный алгоритм, поэтому он не должен занять слишком много времени, но не спешите! Не используйте эту силу, пока не поймете все ее последствия ... (@fcard)
Пример: until
макрос
(@ Исмаэль-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) |>
является спорным. Я удивлен, что толпа еще не стала спорить. (может быть, все просто устали от этого). Существует рекомендация иметь большинство, если не все макросы просто являются вызовом функции, поэтому:
macro until(condition, block) esc(until(condition, block)) end function until(condition, block) quote while !$condition $block end end end
... является более безопасной альтернативой.
## @ простой вызов макроса fcard
Задача: swaps(1/2)
операнды, поэтому swaps(1/2)
дают 2.00
т. 2/1
macro swaps(e) e.args[2:3] = e.args[3:-1:2] e end @swaps(1/2) 2.00
Еще больше макросов от @fcard здесь
Интерполировать и assert
макрос
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: Почему последний $
? A: Он интерполирует, то есть заставляет Джулию eval
эту string(ex)
поскольку выполнение проходит через вызов этого макроса. т. е. если вы просто запустите этот код, это не приведет к какой-либо оценке. Но в тот момент, когда вы assert(foo)
Julia будет вызывать этот макрос, заменяя его «токен AST / Expr» на все, что он возвращает, а $
будет действовать.
Интересный взлом для использования {} для блоков
(@fcard) Я не думаю, что есть что-то техническое сохранение {}
от использования в качестве блоков, на самом деле можно даже каламбурить остаточный синтаксис {}
чтобы заставить его работать:
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
* (вряд ли все еще будет работать, если / когда синтаксис {} будет переназначен)
Итак, сначала Джулия видит токен макроса, поэтому он будет читать / разыгрывать токены до соответствующего end
и создавать что? Expr
с .head=:macro
или что-то? Сохраняет ли она "a+1"
в виде строки или разрывает ее на :+(:a, 1)
? Как посмотреть?
?
(@fcard) В этом случае из-за лексической области a не определено в области @M
s, поэтому использует глобальную переменную ... Я фактически забыл избежать выражения flipplin в моем немом примере, но «работает только в пределах тот же модуль ", который по-прежнему применяется.
julia> module M macro m() :(a+1) end end M julia> a = 1 1 julia> M.@m ERROR: UndefVarError: a not defined
Причина в том, что если макрос используется в любом модуле, отличном от того, в котором он был определен, любые переменные, не определенные в коде, подлежащем расширению, рассматриваются как глобальные модули модуля макроса.
julia> macroexpand(:(M.@m)) :(M.a + 1)
ADVANCED
### @ Исмаэль-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
Макрос Скотта:
"""
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
мусор / необработанный ...
просмотр / сброс макроса
(@ pi-) Предположим, что я просто делаю macro m(); a+1; end
в новом REPL. При отсутствии a
определено. Как я могу его просмотреть? например, есть ли способ «сбросить» макрос? Без его выполнения
(@fcard) Весь код в макросах фактически помещается в функции, поэтому вы можете просматривать только их пониженный или тип-вывод.
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!
Другие способы получения функции макроса:
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)
^ похоже, я могу использовать code_typed
вместо
Как понять eval(Symbol("@M"))
?
(@fcard) В настоящее время каждый макрос имеет связанную с ним функцию. Если у вас есть макрос, называемый M
, то функция макроса называется @M
. Как правило, вы можете получить значение функции, например, eval(:print)
но с помощью функции макроса вам нужно сделать Symbol("@M")
, так как просто :@M
становится Expr(:macrocall, Symbol("@M"))
и оценка, которая вызывает макрорасширение.
Почему параметры code_typed
не отображаются?
(@число Пи-)
julia> code_typed( x -> x^2 )[1] LambdaInfo for (::##5#6)(::Any) :(begin return x ^ 2 end)
^ здесь я вижу один ::Any
параметр, но он, похоже, не связан с токеном x
.
julia> code_typed( print )[1] LambdaInfo for print(::IO, ::Char) :(begin (Base.write)(io,c) return Base.nothing end::Void)
Аналогично здесь; нет ничего, чтобы соединить io
с ::IO
Таким образом, это не может быть полной дамкой представления AST этого конкретного метода print
...?
(@fcard) print(::IO, ::Char)
сообщает только, какой именно метод, это не часть АСТ. Он больше не присутствует в мастерстве:
julia> code_typed(print)[1] CodeInfo(:(begin (Base.write)(io,c) return Base.nothing end))=>Void
(@ pi-) Я не понимаю, что вы подразумеваете под этим. Кажется, это демпинг AST для тела этого метода, нет? Я думал, что code_typed
дает AST для функции. Но, кажется, отсутствует первый шаг, то есть настройка токенов для параметров.
(@fcard) code_typed
предназначен для отображения только AST, но на данный момент он дает полный AST метода в виде LambdaInfo
(0.5) или CodeInfo
(0.6), но большая часть информации опущена при печати на замену. Вам нужно будет проверить поле LambdaInfo
по полю, чтобы получить все детали. dump
собирается наполнить ваш реплик, чтобы вы могли попробовать:
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])
Что дает все значения названных полей метода AST:
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)
См. Lil ' def = print(io::IO, c::Char)
? Вот так! (также слова slotnames = [..., :io, :c]
) Также да, разница в выходе заключается в том, что я показывал результаты на master.
???
(@ Ismael-VC), вы имеете в виду вот так? Общая отправка с символами
Вы можете сделать это следующим образом:
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)
Это для алгоритма Эйлера! Интересно, что @fcard думает об общей отправке символов! --- ^: angel:
Модуль Gotcha
@def m begin a+2 end @m # replaces the macro at compile-time with the expression a+2
Точнее, только работает в пределах уровня модуля, в котором был определен макрос.
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
позволяет это происходить, но отказ от использования его всегда идет вразрез с языком. Хорошая защита для этого заключается в том, чтобы не использовать и вводить имена в макросах, что затрудняет их отслеживание для читателя.
Python `dict` / JSON как синтаксис для литералов` Dict`.
Вступление
Джулия использует следующий синтаксис для словарей:
Dict({k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ)
Хотя Python и JSON выглядят так:
{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}
Для иллюстративных целей мы могли бы также использовать этот синтаксис в Julia и добавить к нему новую семантику (синтаксис Dict
является идиоматическим способом в Julia, который рекомендуется).
Сначала давайте посмотрим, что это за выражение:
julia> parse("{1:2 , 3: 4}") |> Meta.show_sexpr
(:cell1d, (:(:), 1, 2), (:(:), 3, 4))
Это означает, что нам нужно принять это :cell1d
выражение :cell1d
и либо преобразовать его, либо вернуть новое выражение, которое должно выглядеть следующим образом:
julia> parse("Dict(1 => 2 , 3 => 4)") |> Meta.show_sexpr
(:call, :Dict, (:(=>), 1, 2), (:(=>), 3, 4))
Определение макроса
Следующий макрос, хотя и простой, позволяет продемонстрировать такое генерирование и преобразование кода:
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
Давайте посмотрим на полученное макроопределение:
julia> :(@dict {"a": :b, 'c': 1, :d: 2.0}) |> macroexpand
:(Dict("a" => :b,'c' => 1,:d => 2.0))
использование
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
Последний пример в точности эквивалентен:
Dict(
"string" => :b,
'c' => 1,
:symbol => π,
Function => print,
(1:10) => range(1, 10)
)
Misusage
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ₙ}`
Обратите внимание, что у Джулии есть другие способы использования двоеточия :
как таковой вам нужно будет, например, обрезать литеральные выражения с круглыми скобками или использовать функцию range
.