Макрос для переключения или сопоставления с шаблоном Enum - PullRequest
3 голосов
/ 21 мая 2019

Я бы хотел немного синтаксического сахара для включения Enum.Конечно, блок if else работает как и ожидалось:

@enum Fruit apple=1 orange=2 kiwi=3

function talk1(fruit::Fruit)
    if fruit == apple
        "I like apples."
    elseif fruit == orange
        "I like oranges."
    else
        "I like kiwis."
    end
end

Я мог бы даже сделать следующее:

function talk2(fruit::Fruit)
    say = ["I like apples.", "I like oranges.", "I like kiwis."]
    say[Int(fruit)]
end

Но мне не очень нравится подход в talk2, поскольку он выделяет вектор и менее читаем.Я попробовал пакет Match.jl , но я не могу найти соответствие Enum:

using Match

function talk3(fruit::Fruit)
    @match fruit begin
        apple  => "I like apples."
        orange => "I like oranges."
        kiwi   => "I like kiwis."
    end
end
julia> talk3(apple)
"I like apples."

julia> talk3(orange)
"I like apples."

julia> talk3(kiwi)
"I like apples."

Конечно, в макросе @match я могприведите Enum как Int и соответствует Int, но это ухудшает читабельность коммутатора.

Есть ли способ заставить Match.jl работать наEnum?Или есть макрос из другого пакета, который может включить Enum?

Ответы [ 3 ]

3 голосов
/ 21 мая 2019

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

abstract type Fruit end
struct Apple <: Fruit end
struct Orange <: Fruit end
struct Kiwi <: Fruit end

talk(fruit::Apple) = "I like apples."
talk(fruit::Orange) = "I like oranges."
talk(fruit::Fruit) = "I like kiwis."

Как указывает https://pixorblog.wordpress.com/2018/02/23/julia-dispatch-enum-vs-type-comparison/, этот код эффективно встроен компилятором.

1 голос
/ 21 мая 2019

Я написал простой макрос переключения специально для Enum с.Код в значительной степени вдохновлен Match.jl , и ему не хватает универсальности и обработки ошибок Match.@match.Мой макрос @enum_switch реализован следующим образом:

import MacroTools.rmlines

# Assume the correct number of switches are provided for the Enum.
macro enum_switch(v, block_ex)
    block_ex = rmlines(block_ex)  # Remove `LineNumberNode`s from block quote
    pairs = block_ex.args
    ex = nothing

    for p in reverse(pairs)
        if isnothing(ex)
            ex = p.args[3]
        else
            ex = Expr(:if, Expr(:call, :(==), esc(v), p.args[2]), p.args[3], ex)
        end
    end

    ex
end

Он может использоваться для определения talk_switch следующим образом:

@enum Fruit apple=1 orange=2 kiwi=3

function talk_switch(fruit::Fruit)
    @enum_switch fruit begin
        apple  => "I like apples."
        orange => "I like oranges."
        kiwi   => "I like kiwis."
    end
end

И мы можем видеть, что он работает как задумано:

julia> talk_switch(apple)
"I like apples."

julia> talk_switch(orange)
"I like oranges."

julia> talk_switch(kiwi)
"I like kiwis."

Теперь давайте сравним talk_switch с другими предлагаемыми подходами.

function talk_ifelse(fruit::Fruit)
    if fruit == apple
        "I like apples."
    elseif fruit == orange
        "I like oranges."
    else
        "I like kiwis."
    end
end

function talk_array(fruit::Fruit)
    say = ["I like apples.", "I like oranges.", "I like kiwis."]
    say[Int(fruit)]
end

function talk_dict(fruit::Fruit)
    phrases = Dict{Fruit, String}(
        apple  => "I like apples.",
        orange => "I like oranges.",
        kiwi   => "I like kiwis."
    )
    phrases[fruit]
end

abstract type AbstractFruit end
struct Apple <: AbstractFruit end
struct Orange <: AbstractFruit end
struct Kiwi <: AbstractFruit end

const APPLE = Apple()
const ORANGE = Orange()
const KIWI = Kiwi()

talk_type(fruit::Apple) = "I like apples."
talk_type(fruit::Orange) = "I like oranges."
talk_type(fruit::AbstractFruit) = "I like kiwis."

Как и предполагалось, talk_switch и talk_ifelse выдают одинаковый пониженный код:

julia> @code_lowered talk_switch(kiwi)
CodeInfo(
1 ─ %1 = fruit == Main.apple
└──      goto #3 if not %1
2 ─      return "I like apples."
3 ─ %4 = fruit == Main.orange
└──      goto #5 if not %4
4 ─      return "I like oranges."
5 ─      return "I like kiwis."
)

julia> @code_lowered talk_ifelse(kiwi)
CodeInfo(
1 ─ %1 = fruit == Main.apple
└──      goto #3 if not %1
2 ─      return "I like apples."
3 ─ %4 = fruit == Main.orange
└──      goto #5 if not %4
4 ─      return "I like oranges."
5 ─      return "I like kiwis."
)

Наконец, мы можем сравнить производительность различных решений:

julia> using BenchmarkTools

julia> @btime talk_switch(kiwi);
  6.348 ns (0 allocations: 0 bytes)

julia> @btime talk_ifelse(kiwi);
  6.349 ns (0 allocations: 0 bytes)

julia> @btime talk_type(KIWI);
  6.353 ns (0 allocations: 0 bytes)

julia> @btime talk_array(kiwi);
  103.447 ns (1 allocation: 112 bytes)

julia> @btime talk_dict(kiwi);
  861.712 ns (11 allocations: 704 bytes)

Как и ожидалось, talk_switch и talk_ifelse имеют одинаковую производительность, так как они производят одинаковый пониженный код.Интересно, что talk_type также выполняет те же функции, что и talk_switch и talk_ifelse.Наконец, мы видим, что talk_array и talk_dict сильно отстают от трех лучших исполнителей.

1 голос
/ 21 мая 2019

Хотя мне действительно нравится ваша talk2() функция, я думаю, вы могли бы улучшить читабельность, используя Dict:

function talk(fruit::Fruit)
    phrases=Dict{Int,String}([
        (Int(apple)  => "I like apples"), 
        # or: (1->"I like apples"), or: (1,"I like apples")
        (Int(orange) => "I like oranges"),
        (Int(kiwi)   => "I like kiwis")
    ])
   phrases[Int(fruit)]
end

в качестве альтернативы:

function talk(fruit::Fruit)
    phrases=Dict{Fruit,String}(
        apple=>"I like apples",
        orange=>"I like oranges",
        kiwi=>"I like kiwis"
    )
    phrases[fruit]
end

Примечание: это означает, что вам даже не нужно объявлять функцию, а просто положиться на phrases[fruit];однако это приведет к появлению «более слабых» предупреждений, то есть ошибки «ключ не найден» вместо «MethodError» (например, если вы укажете @enum Veg tomato=1), что может усложнить отладку в долгосрочной перспективе.


Если вы хотите использовать Match.jl, я думаю, вам нужно оценить потенциальное совпадение на ::Int(fruit), а не ::Fruit (все три случая в talk3() имеют тип Fruit!),то есть:

function talk3(fruit::Fruit)
    @match Int(fruit_int) begin
        1 => "I like apples."
        2 => "I like oranges."
        3 => "I like kiwis."
    end
end

или с использованием string() части enum:

function talk4(fruit::Fruit)
    @match string(fruit) begin
        "apple" => "I like apples."
        "orange" => "I like oranges."
        "kiwi" => "I like kiwis."
    end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...