使用符号作为轻量级枚举

尽管 @enum 宏对于大多数用例非常有用,但在某些用例中它可能过多。@enum 的缺点包括:

  • 它创建了一种新类型
  • 扩展有点困难
  • 它具有转换,枚举和比较等功能,在某些应用程序中可能是多余的

在需要较轻重量的替代物的情况下,可以使用 Symbol 类型。符号是实习字符串 ; 它们代表字符序列,就像字符串一样,但它们与数字唯一相关。这种独特的关联可实现快速符号相等比较

我们可能会再次实现 Card 类型,这次使用 Symbol 字段:

const ranks = Set([:ace, :two, :three, :four, :five, :six, :seven, :eight, :nine,
                   :ten, :jack, :queen, :king])
const suits = Set([:♣, :♦, :♥, :♠])
immutable Card
    rank::Symbol
    suit::Symbol
    function Card(r::Symbol, s::Symbol)
        r in ranks || throw(ArgumentError("invalid rank: $r"))
        s in suits || throw(ArgumentError("invalid suit: $s"))
        new(r, s)
    end
end

我们实现内部构造函数来检查传递给构造函数的任何不正确的值。与使用 @enum 类型的示例不同,Symbols 可以包含任何字符串,因此我们必须小心我们接受哪种类型的 Symbols。这里注意使用短路条件运算符。

现在我们可以像我们期望的那样构建 Card 对象:

julia> Card(:ace, :♦)
Card(:ace,:♦)

julia> Card(:nine, :♠)
Card(:nine,:♠)

julia> Card(:eleven, :♠)
ERROR: ArgumentError: invalid rank: eleven
 in Card(::Symbol, ::Symbol) at ./REPL[17]:5

julia> Card(:king, :X)
ERROR: ArgumentError: invalid suit: X
 in Card(::Symbol, ::Symbol) at ./REPL[17]:6

Symbols 的一个主要好处是它们的运行时可扩展性。如果在运行时,我们希望接受(例如):eleven 作为新排名,只需运行 push!(ranks, :eleven) 就足够了。@enum 类型无法实现这种运行时可扩展性。