Написание макроса, который возвращает несколько выражений верхнего уровня в Julia - PullRequest
5 голосов
/ 10 марта 2020

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

macro enum_type(type)
    let type = eval(type)
        next = [type]
        methods = []
        counter = 0
        while(!isempty(next))
            let current_type = pop!(next)
                children = subtypes(current_type)
                map(t -> push!(next, t), children)
                push!(methods, :(order(::$current_type) = $(counter += 1)))
            end
        end
        quote
            $(methods...)
        end
    end
end

Кажется, что возвращаемые выражения не оцениваются в верхний уровень. Есть ли способ вернуть несколько выражений верхнего уровня?

Желаемым поведением будет создание метода для каждого типа в иерархии, в качестве примера рассмотрим

@macroexpand @enum_type (AbstractFloat)

Следует написать метод order(...) связывание произвольного числа с каждым типом в дереве типов, начиная с AbstractFloat. На данный момент расширение макроса с аргументом AbstractFloat равно

quote
    #= none:14 =#
    var"#57#order"(::AbstractFloat) = begin
            #= none:10 =#
            1
        end
    var"#57#order"(::Float64) = begin
            #= none:10 =#
            2
        end
    var"#57#order"(::Float32) = begin
            #= none:10 =#
            3
        end
    var"#57#order"(::Float16) = begin
            #= none:10 =#
            4
        end
    var"#57#order"(::BigFloat) = begin
            #= none:10 =#
            5
        end
end

Но ни одно из объявлений метода не выполняется.

1 Ответ

4 голосов
/ 11 марта 2020

Похоже, что ваша проблема не связана с тем, что сгенерированные выражения находятся на верхнем уровне или нет. Это скорее связано с тем, что вы хотите определить обобщенную функцию c с именем order (и несколько связанных с ней методов), но само имя order не сохраняется в расширении макроса: как вы вы можете увидеть в размещенном вами расширении макроса, order был заменен на var"#57order", то есть имя, которое не может иметь ни одна пользовательская функция. Это было сделано, чтобы помочь решить общую проблему с макросами, которая называется гигиена .

Я думаю, что самая маленькая модификация, которую вы могли бы сделать, чтобы макрос работал так, как вы хотите, была бы escape имя функции (order) в сгенерированном выражении, так что имя остается неизменным:

macro enum_type(type)
    let type = eval(type)
        next = [type]
        methods = []
        counter = 0
        while(!isempty(next))
            let current_type = pop!(next)
                children = subtypes(current_type)
                map(t -> push!(next, t), children)
                # see how esc is used to prevent the method name `order` from
                # being "gensymmed"
                push!(methods, :($(esc(:order))(::$current_type) = $(counter += 1)))
            end
        end
        quote
            $(methods...)
        end
    end
end

IIU C, это делает то, что вы хотите (и определения метода все еще не на высшем уровне):

julia> @macroexpand @enum_type AbstractFloat
quote
    #= REPL[1]:14 =#
    order(::AbstractFloat) = begin
            #= REPL[1]:10 =#
            1
        end
    order(::Float64) = begin
            #= REPL[1]:10 =#
            2
        end
    order(::Float32) = begin
            #= REPL[1]:10 =#
            3
        end
    order(::Float16) = begin
            #= REPL[1]:10 =#
            4
        end
    order(::BigFloat) = begin
            #= REPL[1]:10 =#
            5
        end
end

julia> @enum_type AbstractFloat
order (generic function with 5 methods)

julia> order(3.14)
2

Теперь, читая ваш макрос, приходят на ум другие вещи:

  • обычно не одобряется использование eval в теле макроса

  • ваши let блоки здесь на самом деле не нужны; Я предполагаю, что было бы более идиоматическим c опустить их

  • map(f, xs) создает массив, содержащий все значения [f(x) for x in xs]. Если f используется только для побочных эффектов, вместо него следует использовать foreach. (РЕДАКТИРОВАТЬ: как отмечает @CameronBieganek, append! делает именно то, что нужно в этом конкретном случае c)

  • Я думаю, что для такой задачи, где метапрограммирование используется для генерации кода верхнего уровня из (почти) ничего, вместо преобразования (предоставленного пользователем) выражения (возможно, в заданную область / контекст), I будет использовать @eval вместо макроса.

Так что я бы, возможно, использовал такой код:

function enum_type(type)
    next = [type]
    counter = 0
    while(!isempty(next))
        current_type = pop!(next)
        append!(next, subtypes(current_type))

        @eval order(::$current_type) = $(counter += 1)
    end
end

, который производит те же результаты:

julia> enum_type(AbstractFloat)

julia> order(3.14)
2
...