Specialized Proxies: Symbols#
Another specialized type of proxy is the symbol proxy. It manages a Julia-side Base.Symbol
.
We can create a symbol proxy in the following ways:
// new unnamed proxy
auto unnamed_symbol = Symbol("symbol_value");
// new named proxy
auto named_symbol = Main.new_symbol("symbol_var", "symbol_value");
// from already existing proxy
auto non_symbol_proxy_managing_symbol = Main.safe_eval("return :symbol_value");
auto unnamed_symbol = non_symbol_proxy_managing_symbol.as<Symbol>();
Where, unlike with jluna::Proxy
, the value the proxy is pointing to is asserted to be of type Base.Symbol
.
Symbol Hashing#
The main additional functionality jluna::Symbol
brings is that of constant time hashing.
A hash is essentially a UInt64
we assign to things as a label. In Julia, hashes are unique and there a no hash collisions. This means if A != B
then hash(A) != hash(B)
and, furthermore, if hash(A) == hash(B)
then A === B
.
Julia Hint:
(==)
checks if the value of two variables is the same.(===)
checks whether both variables values have the exact identical location in memory - if they are the same instance.
Unlike with other classes, Base.Symbol
s hash is pre-computed, making it much faster to hash.
We can access the hash of a symbol proxy using .hash()
. To get the symbol as a string, we use static_cast
:
// create symbol
auto symbol = Symbol("abc");
// print name and hash
std::cout << "name: " << static_cast<std::string>(symbol) << std::endl;
std::cout << "hash: " << symbol.hash() << std::endl;
name: abc
hash: 16076289990349425027
In most cases, it is impossible to predict which hash will be assigned to which symbol. This also means for Symbols s1
and s2
if string(s1) < string(s2)
this does not mean hash(s1) < hash(s2)
. This is very important to realize, symbol comparison is not lexicographical comparison.
Having taken note of this, jluna::Symbol
provides the following comparison operators:
/// (*this).hash() == other.hash()
bool operator==(const Symbol& other) const;
/// (*this).hash() != other.hash()
bool operator!=(const Symbol& other) const;
/// (*this).hash() < other.hash()
bool operator<(const Symbol& other) const;
/// (*this).hash() <= other.hash()
bool operator<=(const Symbol& other) const;
/// (*this).hash() >= other.hash()
bool operator>=(const Symbol& other) const;
/// (*this).hash() > other.hash()
bool operator>(const Symbol& other) const;
To further illustrate the way symbols are compared, consider the following example using std::set
, which orders its elements according to their user-defined comparison operators:
// create set
auto set = std::set<Symbol>();
// add newly constructed symbols to it
for (auto str : {"abc", "bcd", "cde", "def"})
set.insert(Symbol(str));
// print in order
for (auto symbol : set)
std::cout << symbol.operator std::string() << " (" << symbol.hash() << ")" << std::endl;
cde (10387276483961993059)
bcd (11695727471843261121)
def (14299692412389864439)
abc (16076289990349425027)
We see that, lexicographically, the symbols are out of order. They are, however, ordered properly according to their hashes.