Although the @enum
macro is quite useful for most use cases, it can be excessive in some use cases. Disadvantages of @enum
include:
In cases where a lighter-weight alternative is desired, the Symbol
type can be used. Symbols are interned strings; they represent sequences of characters, much like strings do, but they are uniquely associated with numbers. This unique association enables fast symbol equality comparison.
We may again implement a Card
type, this time using Symbol
fields:
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
We implement the inner constructor to check for any incorrect values passed to the constructor. Unlike in the example using @enum
types, Symbol
s can contain any string, and so we must be careful about what kinds of Symbol
s we accept. Note here the use of the short-circuit conditional operators.
Now we can construct Card
objects like we expect:
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
A major benefit of Symbol
s is their runtime extensibility. If at runtime, we wish to accept (for example) :eleven
as a new rank, it suffices to simply run push!(ranks, :eleven)
. Such runtime extensibility is not possible with @enum
types.