[英]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
@match
为Int
并匹配Int
,但这会妨碍交换机的可读性。
Is there a way to get Match.jl to work on an Enum
? 有没有办法让Match.jl在
Enum
上工作? 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)
上的潜在匹配,而不是::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
or using the string()
part of the enum
: 或使用
enum
的string()
部分:
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_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."
)
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_switch
和talk_ifelse
具有相同的性能,因为它们产生相同的降低代码。 Interestingly, talk_type
also performs the same as talk_switch
and talk_ifelse
. 有趣的是,
talk_type
也和talk_switch
和talk_ifelse
。 Finally, we can see that talk_array
and talk_dict
lag far behind the top three performers. 最后,我们可以看到
talk_array
和talk_dict
远远落后于前三名。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.