@enum macro is quite useful for most use cases, it can be excessive in some use cases. Disadvantages of
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
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
Symbols can contain any string, and so we must be careful about what kinds of
Symbols 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:5 julia> Card(:king, :X) ERROR: ArgumentError: invalid suit: X in Card(::Symbol, ::Symbol) at ./REPL:6
A major benefit of
Symbols 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