UndefVarError в макросе Julia - PullRequest
       43

UndefVarError в макросе Julia

1 голос
/ 24 февраля 2020

Чтобы помочь моей отладке (а также чтобы лучше понять, как работают макросы Julia), я пытаюсь определить простой макрос, который окружает блоки кода с помощью уведомлений «Вход» и «Выход». Вот что я придумала до сих пор:

macro dbg(block_title, expr)
    quote
        title = $block_title
        println("Entering $title")
        $expr
        println("Leaving  $title")
    end
end

На первый взгляд кажется, что я делаю то, что хочу:

julia> @dbg "first test" begin
           println("does it work?")
       end
Entering first test
does it work?
Leaving  first test

однако, как только задействуются переменные больше ничего не работает, и я получаю UndefVarError для всех переменных доступа. Похоже, область видимости внутри и снаружи макроса различна:

julia> @dbg "initialization" begin
           foo = rand(10)
           println("foo = ", foo)
       end
Entering initialization
foo = [0.9178016919066918, 0.6004694971609528, 0.5294790810682284, 0.04208146400653634, 0.09271603217172952, 0.2809448815925, 0.68236281020963, 0.8313876607106496, 0.07484095574744898, 0.14099531301938573]
Leaving  initialization

julia> foo
ERROR: UndefVarError: foo not defined

Что я делаю не так?

1 Ответ

4 голосов
/ 24 февраля 2020

Короче говоря, вам не хватает понятия макро-гигиены и, в частности, функции esc.

Хотя эта часть документации является хорошее чтение для тех, кто хочет разрабатывать свои собственные макросы, давайте попробуем немного рассказать о том, что делает гигиена макросов в этом конкретном примере, и как вы можете что-то исправить.

Полезный способ отладки макросов предоставляется @macroexpand:

julia> @macroexpand @dbg "initialization" begin
           foo = rand(10)
           println("foo = ", foo)
       end
quote
    #= REPL[1]:3 =#
    var"#32#title" = "initialization"
    #= REPL[1]:4 =#
    Main.println("Entering $(var"#32#title")")
    #= REPL[1]:5 =#
    begin
        #= REPL[5]:2 =#
        var"#33#foo" = Main.rand(10)
        #= REPL[5]:3 =#
        Main.println("foo = ", var"#33#foo")
    end
    #= REPL[1]:6 =#
    Main.println("Leaving  $(var"#32#title")")
end

Оставляя в стороне все комментарии в пределах маркеров #= ... =#, мы видим почти тот код, который хотели получить: блок кода пользователя был окружен инструкциями, печатающими " Вход "и" уход "уведомления. Однако есть одно заметное отличие: имена переменных, такие как foo или title, были заменены на странно выглядящие имена, такие как var"#33#foo" или var"#32#title". Это то, что называется « макро-гигиена », и это помогает избежать конфликтов, которые могут возникнуть между переменными, используемыми в самом макросе (например, title в этом примере), и переменными, используемыми в блоке кода. предоставляется в качестве аргумента макроса (например, foo здесь). (Например, подумайте, что бы произошло, если бы вы использовали @dbg в блоке кода, который определяет переменную title.)

Джулия ошибается, и таким образом защищает все переменные, появляющиеся в макрос. Если вы хотите отключить это для выбранных частей выражения, созданного макросом, вы можете заключить эти подвыражения в функцию esc. В вашем примере вы должны, например, экранировать предоставленное пользователем выражение:

macro dbg(block_title, expr)
    quote
        title = $block_title
        println("Entering $title")
        $(esc(expr))
        println("Leaving  $title")
    end
end

Теперь все должно работать так, как вы хотите:

julia> @dbg "initialization" begin
           foo = rand(10)
           println("foo = ", foo)
       end
Entering initialization
foo = [0.2955287439482881, 0.8989053281359838, 0.27751430906108343, 0.4920810199867245, 0.7633806735297282, 0.34535540650110597, 0.7099231627594489, 0.39978144801175564, 0.9104888704503833, 0.1983996781283539]
Leaving  initialization

julia> @dbg "computation" begin
           foo .+= 1
       end
Entering computation
Leaving  computation

julia> foo
10-element Array{Float64,1}:
 1.295528743948288 
 1.8989053281359838
 1.2775143090610834
 1.4920810199867245
 1.7633806735297282
 1.345355406501106 
 1.709923162759449 
 1.3997814480117556
 1.9104888704503833
 1.198399678128354 
...