Поиск…


Синтаксис

  • имя макроса (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 .



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow