Julia Language
메타 프로그래밍
수색…
통사론
- 매크로 이름 (예) ... 끝
- 따옴표 ... 끝
- : (...)
- $ x
- 메타 .quot (x)
- QuoteNode (x)
- esc (x)
비고
Julia의 메타 프로그래밍 기능은 Lisp과 유사한 언어의 영향을 많이받으며 일부 Lisp 배경을 가진 사람들에게 익숙한 것처럼 보일 것입니다. 메타 프로그래밍은 매우 강력합니다. 올바르게 사용하면 더 간결하고 읽기 쉬운 코드로 이어질 수 있습니다.
quote ... end
는 quasiquote 구문입니다. 평가되는 표현식 대신 단순히 구문 분석됩니다. quote ... end
표현식의 결과는 최종 추상 구문 트리 (AST)입니다.
:(...)
구문은 quote ... end
구문과 비슷하지만 더 가볍습니다. 이 구문은 quote ... end
보다 간결합니다.
quasiquote 내부에서, $
연산자는 특수하며 AST에 인수를 보간 합니다. 인수는 AST에 직접 연결되는 표현식이어야합니다.
Meta.quot(x)
함수는 인수를 인용합니다. 표현식과 기호를 문자 그대로 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
로 평가되는 동안 본문을 실행 true
. until
루프를 구현하면 조건이 true
로 평가 될 때 until
루프가 실행됩니다.
Julia에서 우리는 조건이 충족 될 때 본문을 실행하기 위해 멈추는 @until
매크로를 작성하여이 작업을 수행 할 수 있습니다.
macro until(condition, expression) quote while !($condition) $expression end end |> esc end
여기에서 우리는 함수 체인 구문 사용했다 |>
호출하는 것과 같습니다, esc
전체에 기능 quote
블록을. esc
함수는 매크로 위생이 매크로의 내용에 적용되는 것을 방지합니다. 매크로가 없으면 외부 변수와의 충돌을 방지하기 위해 매크로에서 범위가 지정된 변수의 이름이 바뀝니다. 자세한 내용은 매크로 위생 에 관한 Julia 문서를 참조하십시오.
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 및 Expr (: 따옴표)
Julia 함수를 사용하여 무언가를 인용하는 세 가지 방법이 있습니다.
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
"따옴표"는 무엇을 의미하며 무엇이 좋은가? 인용문을 사용하면 Julia가 표현을 특수한 형식으로 해석되지 않도록 보호 할 수 있습니다. 일반적인 사용 사례는 기호로 평가되는 항목을 포함해야하는 표현식 을 생성 할 때입니다. 예를 들어, 이 매크로 는 기호로 평가되는 식을 반환해야합니다. 단순히 기호를 반환하는 데는 작동하지 않습니다.
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
에 할당 된 것은 없으므로 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
과 QuoteNode
의 차이는 무엇이며 어떤 것을 사용해야합니까? 거의 모든 경우에 차이점은 중요하지 않습니다. 아마도 가끔 사용하는 작은 안전 QuoteNode
대신 Meta.quot
. 차이를 탐구하는 것은 줄리아 식과 매크로가 어떻게 작동하는지에 대한 정보를 제공합니다.
Meta.quot
과 QuoteNode
의 차이점 설명
엄지 손가락의 규칙은 다음과 같습니다.
- 보간을 필요로하거나 지원하고 싶다면,
Meta.quot
; - 보간을 허용 할 수 없거나 원하지 않으면
QuoteNode
사용QuoteNode
.
즉, 차이가 있다는 것입니다 Meta.quot
동안 인용 된 것은 내 보간을 할 수 QuoteNode
어떤 보간에서 인수를 보호합니다. 보간을 이해하려면 $
표현을 언급하는 것이 중요합니다. Julia에는 $
표현이라고하는 일종의 표현이 있습니다. 이 표현식은 탈출을 허용합니다. 예를 들어 다음 식을 생각해보십시오.
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)
우리가 이러한 종류의 표현을 만들기위한 매크로를 작성하고 있다고 가정 해 봅시다. 매크로는 위의 ex
에서 1
을 대체 할 인수를 취합니다. 이 인수는 물론 모든 표현식이 될 수 있습니다. 여기에 우리가 원하는 것이 아니라는 것이 있습니다.
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
? 추측 하듯이 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
를 사용해야 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
를 사용 QuoteNode
결코 해로울 수 없습니다. 그러나 차이점을 이해하면 Meta.quot
이 더 나은 선택 일 수있는 곳과 이유를 Meta.quot
수 있습니다.
이 긴 연습 문제는 합리적으로 적용하기에는 너무 복잡하다는 예가 있습니다. 따라서 앞에서 언급 한 다음과 같은 경험 법칙을 정당화했습니다.
- 보간을 필요로하거나 지원하고 싶다면,
Meta.quot
; - 보간을 허용 할 수 없거나 원하지 않으면
QuoteNode
사용QuoteNode
.
Expr (: quote)은 어떻습니까?
Expr(:quote, x)
은 Meta.quot(x)
와 같습니다. 그러나, 후자는 더 관용적이며 선호된다. metaprogramming을 많이 사용하는 코드의 경우 using Base.Meta
행을 사용하는 경우가 자주 사용됩니다. Meta.quot
를 단순히 quot
로 Meta.quot
수 있습니다.
안내서
π의 메타 프로그래밍 비트 및 봅
목표 :
적절한 컨텍스트에서 개념을 소개하는 목표로하는 기능적 / 유용 / 비 추상적 인 예제 (예 :
@swap
또는@assert
)를 통해@assert
설명의 단락보다는 개념을 설명 / 보여주기를 선호한다.
'필수 읽기'를 다른 페이지에 연결하지 마십시오. 서술이 중단됩니다.
현명한 순서로 제시하면 학습이 쉬워집니다.
자원:
julialang.org
wikibook (@Cormullion)
5 개의 레이어 (Leah Hanson)
SO-Doc 인용 (@ TotalVerb)
SO-Doc - 합법적 식별자가 아닌 기호 (@TotalVerb)
그래서 : Julia (@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
해시 키 예제 :
number_names = Dict{Symbol, Int}() number_names[:one] = 1 number_names[:two] = 2 number_names[:six] = 6
(고급) (@fcard) :foo
일명 :(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 ...
기본적으로 Symbols을 간단한 문자열로 취급 할 수 있습니다. 그것은 그들이 원하는 것을위한 것이 아니라 당신이 할 수있는 것입니다. Julia 's Base 자체가 그것을합니다. print_with_color(:red, "abc")
는 붉은 색 abc를 출력합니다.
Expr (대서양 표준시)
(거의) Julia의 모든 것은 표현식입니다. 예를 들어 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
의 사용 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
을 사용 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)
( Julia metaprogramming에서 다양한 인용 메커니즘을 명확히하십시오.
$ 와 : (...) 어떻게 든 서로 역전합니까?
:(foo)
는 "값을 보지 말고, 표현식을 봅니다" $foo
는 "표현식을 값으로 변경 함을 의미합니다"
:($(foo)) == foo
. $(:(foo))
는 오류입니다. $(...)
는 연산이 아니며 그 자체로는 아무 것도하지 않는다. 이것은 "interpolate this!"이다. 인용 구문이 사용하는 부호. 즉 그것은 단지 견적 안에 존재합니다.
$
foo
는 eval(
foo
)
와 같은가요?
아니! 컴파일시에 $foo
가 교환된다. 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
Expr
을 낮추기 위해 expand
줄리아가 소스 코드를 문자열로 가져 와서 Expr
tree (AST)로 토큰 화하고, 모든 매크로 (여전히 AST)를 확장하고, AST를 낮추고 , LLVM으로 변환하는 방법을 설명하는 5 개의 레이어 (Leah Hanson) (그리고 그 이상 - 지금은 우리가 넘어서는 것을 걱정할 필요가 없습니다!)
질문 : code_lowered
는 함수에서 작동합니다. Expr
을 낮출 수 있습니까? A : 응!
# 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
... 비 - 낮추어 진 AST를 반환하지만 모든 매크로가 확장되었습니다.
esc()
esc(x)
는 Expr(:escape, x)
과 같은 "위생을 적용하지 마십시오"라는 Expr을 반환합니다. 위생 매크로 자체에 포함 된 유지 것입니다, 당신은 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
이는 p
에 대한 macro
범위를 먼저 찾게 될 것이고, 로컬 p
즉 매개 변수 p
찾을 것입니다 (예, esc
하지 않고 계속 p
액세스하면 매크로는 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
는 그렇지 않습니다. Julia는 tmp
에 대한 고유 한 식별자를 만들고 gensym
, gensym
으로 직접 할 수있는 것입니다.
julia> gensym(:tmp) Symbol("##tmp#270")
그러나 : 거기에 잡았다 :
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
매크로를 다른 모듈에있는 경우, 또 다른 것은 줄리아의 매크로 위생은 (같은 매크로의 반환 표현의 내부에 할당되지 않은 모든 변수하게,이다 않습니다 tmp
이 경우) 현재 모듈의 전역을, 그래서 $p
하게 Swap.$p
, 마찬가지로 $q
-> Swap.$q
.
일반적으로 매크로 범위 밖에있는 변수가 필요하다면 esc를해야합니다. 따라서 표현식의 LHS 또는 RHS에 관계없이 esc(p)
와 esc(q)
를 수행해야합니다.
사람들은 이미 몇 번 gensym
언급 gensym
그리고 곧 당신은 gensym
몇 gensym
가지고 전체 표현을 빠져 나가는 것의 어두운 측면에 유혹 될 것입니다. 그러나 ... 시도하기 전에 위생이 어떻게 작동 하는지를 이해하십시오. 그것보다 똑똑해! 그것은 특히 복잡한 알고리즘이 아니기 때문에 너무 오래 걸리지는 않겠지 만 서둘지 마라! 당신이 그 모든 파급 효과를 이해할 때까지 그 힘을 사용하지 마라. (@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/ko/release-0.5/manual/metaprogramming/#building-an-advanced-macro
macro assert(ex) return :( $ex ? nothing : throw(AssertionError($(string(ex)))) ) end
Q : 왜 마지막 $
? A : 이것은 보간합니다. 즉,이 매크로의 호출을 통해 실행이 통과 할 때 Julia가 해당 string(ex)
을 eval
하게 string(ex)
. 즉, 해당 코드를 실행하면 평가가 강제 실행되지 않습니다. 하지만 당신이 assert(foo)
Julia는 반환하는 것이 무엇이든지간에 'AST token / 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) 어휘 범위 때문에이 경우 전역 변수를 사용하기 때문에 @M
범위에서 정의되지 않는다 . 동일한 모듈 "의 일부가 여전히 적용됩니다.
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)
많은
### @ 이스마엘 -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로 macro m(); a+1; end
. 아니오 정의. 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
이라고 @M
. 일반적으로 eval(:print)
와 같이 함수의 값을 얻을 수 있지만, :@M
이 Expr(:macrocall, Symbol("@M"))
:@M
Expr(:macrocall, Symbol("@M"))
:@M
Symbol("@M")
이되기 때문에 Expr(:macrocall, Symbol("@M"))
매크로 확장을 유발하는지 평가합니다.
왜 code_typed
매개 변수를 표시하지 code_typed
?
(@ pi-)
julia> code_typed( x -> x^2 )[1] LambdaInfo for (::##5#6)(::Any) :(begin return x ^ 2 end)
^ 여기에 하나 ::Any
param이 표시되지만 토큰 x
와 연결되지 않은 것 같습니다.
julia> code_typed( print )[1] LambdaInfo for print(::IO, ::Char) :(begin (Base.write)(io,c) return Base.nothing end::Void)
^ 유사하게 여기에서. io
를 ::IO
에 연결할 수있는 것이 아무것도 없습니다. 따라서 확실히이 방법은 특정 print
방법의 AST 표현을 완전히 덤프 할 수는 없지만 ...?
(@fcard) print(::IO, ::Char)
는 어떤 메소드인지 알려주고, AST의 일부가 아닙니다. 더 이상 마스터에 존재하지 않습니다.
julia> code_typed(print)[1] CodeInfo(:(begin (Base.write)(io,c) return Base.nothing end))=>Void
(@ pi-) 나는 그것이 무슨 뜻인지 이해하지 못합니다. 그 방법의 시체를 위해 대서양 표준시를 버리는 것 같습니다. 나는 code_typed
가 함수에 대해 AST를 제공한다고 생각했다. 하지만 첫 번째 단계, 즉 매개 변수에 대한 토큰을 놓치고있는 것 같습니다.
(@fcard) code_typed
는 본문의 AST 만 표시하기위한 것이지만, 이제는 LambdaInfo
(0.5) 또는 CodeInfo
(0.6) 형태로 완전한 AST를 제공하지만 많은 정보가 생략됩니다 repl에 인쇄 할 때. 모든 세부 사항을 확인하려면 LambdaInfo
필드를 필드별로 검사해야합니다. dump
가 당신의 repl을 넘치게하려고 시도 할 수 있습니다 :
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]
part) 또한 네, 출력의 차이는 master에서 결과를 보여주기 때문입니다.
???
(@ 이스마엘 - 벤처) 당신은 이런 뜻인가요? 기호로 일반 발송
다음과 같이 할 수 있습니다.
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가 일반 심볼 발송에 대해 어떻게 생각하는지 궁금합니다. --- ^ : 천사 :
모듈 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
이런 일이 일어나지 않도록하지만, 항상 그것을 사용하는 것으로 기본 설정하는 것은 언어 디자인에 어긋납니다. 이것에 대한 좋은 방어 방법은 사람이 매크로를 사용하거나 매크로에서 이름을 사용하지 못하도록하여 사람의 독자를 추적하기 어렵게 만드는 것입니다.
파이썬`dict` / JSON은`Dict` 리터럴을위한 문법과 비슷합니다.
소개
Julia는 사전에 다음 구문을 사용합니다.
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
표현식을 변환하거나 다음과 같은 새로운 표현식을 반환하십시오 :
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)
)
잘못된
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
, 예를 들어, 기능.