简体   繁体   English

用于与Enum匹配的开关或模式的宏

[英]Macro for switch or pattern matching with an Enum

I would like to have some syntactic sugar for switching on an Enum . 我希望有一些语法糖用于打开Enum Of course, an if else block works as expected: 当然, 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

I could even do the following: 我甚至可以做以下事情:

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

But I don't really like the approach in talk2 , since it allocates a vector and is less readable. 但我真的不喜欢talk2的方法,因为它分配了一个向量而且不太可读。 I tried the Match.jl package, but I can't seem to match an Enum : 我尝试了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."

Of course, in the @match macro I could cast the Enum as an Int and match the Int , but that hampers the readability of the switch. 当然,在@match宏中,我可以将Enum @matchInt并匹配Int ,但这会妨碍交换机的可读性。

Is there a way to get Match.jl to work on an Enum ? 有没有办法让Match.jlEnum上工作? Or is there a macro from a different package that can switch on an Enum ? 或者是否有来自不同包的宏可以打开Enum

This is perhaps the main reason to use types instead of enums. 这可能是使用类型而不是枚举的主要原因。 Then dispatch handles this for you: 然后dispatch为您处理:

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."

As https://pixorblog.wordpress.com/2018/02/23/julia-dispatch-enum-vs-type-comparison/ point out, this code is efficiently inlined by the compiler. 正如https://pixorblog.wordpress.com/2018/02/23/julia-dispatch-enum-vs-type-comparison/指出的那样,编译器会有效地内联此代码。

Although I actually like your talk2() function, I guess you could improve on readability by using a Dict : 虽然我实际上喜欢你的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

alternatively: 或者:

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

Note: this means you wouldn't even need to declare a function, but could just rely on phrases[fruit] instead; 注意:这意味着你甚至不需要声明一个函数,但可以只依赖phrases[fruit] ; this will however give "weaker" warnings, ie a "key not found" error instead of a "MethodError" (if you give it a @enum Veg tomato=1 , for example), which may make debugging more difficult in the long run. 然而,这会给出“弱”警告,即“未找到键”错误而不是“MethodError”(例如,如果你给它一个@enum Veg tomato=1 ),这可能会使调试更加困难。


If you want to use Match.jl , I think you need to be evaluate a potential match on ::Int(fruit) , not ::Fruit (all three cases in talk3() are of type Fruit!), ie: 如果你想使用Match.jl ,我认为你需要评估::Int(fruit)上的潜在匹配,而不是::Fruittalk3()中的所有三种情况都是Fruit!),即:

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

or using the string() part of the enum : 或使用enumstring()部分:

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

I wrote a simple switch macro specifically for Enum s. 我专门为Enum编写了一个简单的开关宏。 The code is heavily inspired by Match.jl , and lacks the generality and error handling of Match.@match . 该代码受Match.jl的启发,缺乏Match.@match的通用性和错误处理。 My @enum_switch macro is implemented as follows: 我的@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

It can be used to define a talk_switch as follows: 它可用于定义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

And we can see it works as intended: 我们可以看到它按预期工作:

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

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

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

Now let's compare talk_switch to the other proposed approaches. 现在让我们将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."

As intended, talk_switch and talk_ifelse produce the same lowered code: 按照预期, 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."
)

Finally, we can benchmark the performance of the various solutions: 最后,我们可以对各种解决方案的性能进行基准测试:

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)

As expected, talk_switch and talk_ifelse have the same performance since they produce the same lowered code. 正如所料, talk_switchtalk_ifelse具有相同的性能,因为它们产生相同的降低代码。 Interestingly, talk_type also performs the same as talk_switch and talk_ifelse . 有趣的是, talk_type也和talk_switchtalk_ifelse Finally, we can see that talk_array and talk_dict lag far behind the top three performers. 最后,我们可以看到talk_arraytalk_dict远远落后于前三名。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM