Передача макропеременной в функцию для интерполяции

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

macro small_bad(item)
    quote
        $(use_val(esc(item)))
    end
end

function use_val(val)
    quote
        if $val == 1
            1
        elseif $val == 2
            2
        else
            -1
        end
    end
end

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

macro small_good(item)
    quote
        begin
            val = $(esc(item))
            $(begin
                  use_val(val)
              end)
        end
    end
end

Но затем я понимаю, что val не определено в интерполяции в @small_good.

Я также пытался передать use_val(:val), но это тоже не удается, потому что система макросов переименует val во что-то другое.

Как я могу этого добиться?

EDIT: Учитывая первый ответ, я попробовал это в своем реальном коде.

macro match(item, arms)
    var = gensym(:var)
    quote
        let $var = $(esc(item))
            $(begin
                code = :nothing
                for e in reverse(filter((e) -> e isa Expr, arms.args))
                    code = make_match(var, e, code)
                end
                code
            end)
        end
    end
end

и получил UndefVarError: ##var#253 not defined

суть с полным кодом здесь

Отказ от ответственности: я знаю, что макрос @match уже реализован в пакете Match.jl, я повторно реализую его подмножество в качестве учебного упражнения

ИЗМЕНИТЬ 2:

Я понял. После использования предложения Франсуа Февота мне пришлось изменить мою реальную версию use_val, которая на самом деле выполняла $(esc(val)) вместо $val.

Ошибка с моей стороны, что я не включил эту деталь. Обновлю суть, чтобы отразить это


person Mendess    schedule 21.05.2020    source источник


Ответы (1)


Если я понимаю, что вы хотите, это должно работать:

macro small(item)
    var = gensym(:var)
    quote
        let $var = $(esc(item))
            $(use_val(var))
        end
    end
end

function use_val(val)
    quote
        if $val == 1
            1
        elseif $val == 2
            2
        else
            -1
        end
    end
end

Он расширяется до чего-то вроде:

julia> using MacroTools
julia> MacroTools.@expand @small myexpr
quote
    let octopus = myexpr
        begin
            if octopus == 1
                1
            elseif octopus == 2
                2
            else
                -1
            end
        end
    end
end

и не имеет проблем ни с гигиеной, ни с множественными оценками:

# Testing in a local scope introduced by `let`
# is a good way to check for hygiene issues
julia> let arg = [1]
           @small pop!(arg)
       end
1




Теперь я предполагаю, что большая часть сути вашей первоначальной проблемы была потеряна в процессе создания MWE, потому что все это по существу эквивалентно:

julia> function small(val)
           if val == 1
               1
           elseif val == 2
               2
           else
               -1
           end
       end
small (generic function with 1 method)

julia> let args = [1]
           small(pop!(args))
       end
1
person François Févotte    schedule 21.05.2020
comment
Я не мог заставить его работать. Я обновлю сообщение тем, что я пытался сделать, используя ваше предложение, возможно, привнося более соответствующий контекст. - person Mendess; 21.05.2020