用于与枚举进行开关或模式匹配的宏

时间:2019-05-20 23:33:57

标签: enums julia

我想使用一些语法糖来打开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 个答案:

答案 0 :(得分:3)

这也许是使用类型而不是枚举的主要原因。然后调度为您处理:

someSet

https://pixorblog.wordpress.com/2018/02/23/julia-dispatch-enum-vs-type-comparison/指出,编译器可以有效地内联此代码。

答案 1 :(得分:1)

尽管我实际上喜欢您的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()中的所有三种情况都是类型水果!),即:

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

或使用string()的{​​{1}}部分:

enum

答案 2 :(得分:1)

我专门为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_switchtalk_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_switchtalk_ifelse具有相同的性能,因为它们产生相同的降低的代码。有趣的是,talk_type的性能也与talk_switchtalk_ifelse相同。最后,我们可以看到talk_arraytalk_dict远远落后于前三名。