[Tutorial] Ruby: Symbols
Posted: Thu Mar 18, 2021 7:12 am
The question came up. What is a symbol? To answer it, we will first have a look at a totally different topic. Logos! Have a look at this one:
If you thought of a Cheeseburger, that's fine.
If you thought of a McFlurry, that's fine.
If you thought of hot coffee, that's fine.
If you thought of a big company acting worldwide, that's fine.
It's fine, because the logo stands for all of it. It is not just a picture for a specific thing, it's the entirety of the McDonald's cosmos. A famous graphic designer described it like so: "A logo is a flag, a signature, an escutcheon, a street sign. A logo does not sell (directly), it identifies."
And indeed, a logo is the biggest part of "corporate identity". And if you understand, that one logo stands for several different things, as it identifies a whole company, you are already prepared to understand Ruby's symbols.
A symbol is a unique identifier. It is not a variable, it is not a string, although it reminds on a string. Everytime you write in your RubyEdit the following, it creates a new object.
To prevent that, we can make use of variables.
But we can easily change the object, @greet is pointing to.
And we can change mutable objects, too. In programming, mutable means that the object itself can change, immutable means it can't be changed ever.
There's something missing here and that's a thing that is kind of immutable and can't point to different objects. And that's the birth of symbols. A symbol is written with a preceding double-colon, to differentiate from variables, contants, classes, etc.
The symbol we created is now added to a table of all symbols used by Ruby. All of them are unique, :greet is only in there one time, no matter how often and under what circumstances we use it! You can check this table, by using the Symbol class's method.
If you do that, you will notice that there are no rules as to how you name it, other than a preceding double-colon. In fact, you will find symbols like
:Class
:"core#set_method_alias"
:[]=
and even the name of the class Symbol, :Symbol
Now you might think, that symbols still are much like strings. Maybe not in how they are handled by Ruby, but in their behavior. And yes, that's not wrong. A symbol is an identifier, and as such needs a descriptive name, which is almost as if it were a string. And you can indeed convert a symbol to a string, and a string to a symbol.
I want to point out, that you should never use #to_sym, unless you know exactly what you are doing! Since symbols stay in memory, you can create a memory leak, when, for example, automatically creating symbols from a user inputted string over and over again (every different string will become an additional symbol, sitting in memory until the app/plugin is closed)!
But of course there are advantages. For once, there's only ever one symbol of the same label, which means a small lookup table where it can be found much quicker. Then they are immutable and therefore instantiating them is faster than instantiating strings. Execution time can be reduced as much as to a third of the strings' time.
The best use case for explaining symbols is Hashes, as they use key - value pairs. In other languages (they don't have symbols), you have to use strings as keys.
But we can use symbols and save time. Even better, symbols are so useful in hashes that there is a specific way of writing a key-value pair!
Compare the two ways of using a string or a symbol as key, and you see the clear advantage of identifiers over strings even just for the state of programming, aside from all optimizations! In the first example, you can't tell, which one is data, because both are strings. In the other examples it is obvious what is data and what is key. And even more, you can have nested Hashes, and use the same symbols, without any compromise. The same symbol in another Hash represents another type of data, just like a logo stands for fries or cheeseburger (or both), depending on your appetite.
In short, keep the strings for users of your app/plugin. Keep the symbols in your code, use them whenever you need an identifier and save time over strings.
If you thought of a Cheeseburger, that's fine.
If you thought of a McFlurry, that's fine.
If you thought of hot coffee, that's fine.
If you thought of a big company acting worldwide, that's fine.
It's fine, because the logo stands for all of it. It is not just a picture for a specific thing, it's the entirety of the McDonald's cosmos. A famous graphic designer described it like so: "A logo is a flag, a signature, an escutcheon, a street sign. A logo does not sell (directly), it identifies."
And indeed, a logo is the biggest part of "corporate identity". And if you understand, that one logo stands for several different things, as it identifies a whole company, you are already prepared to understand Ruby's symbols.
A symbol is a unique identifier. It is not a variable, it is not a string, although it reminds on a string. Everytime you write in your RubyEdit the following, it creates a new object.
Code: Select all
"Hallo" #first object
"Hallo" #second object
"Hallo" #third object, etcTo prevent that, we can make use of variables.
Code: Select all
@greet = "Hallo" #"hallo" is created, let's ID it as object_1
@greet #points to object_1
@greet #points to object_1
@greet #points to object_1, etcBut we can easily change the object, @greet is pointing to.
Code: Select all
@greet = "moin" #"moin" is created, let's ID it as object_2
@greet #points to object_2
@greet #points to object_2
@greet #points to object_2, etcAnd we can change mutable objects, too. In programming, mutable means that the object itself can change, immutable means it can't be changed ever.
Code: Select all
@var_a = "moin" #object moin is created and var_a associated with it
@var_b = @var_a #a second variable is associated with object moin
@var_b.upcase! #an exclamation mark tells you the method is changing the object itself
#both, @var_a and @var_b now point to object moin, which reads "MOIN" instead of "moin"There's something missing here and that's a thing that is kind of immutable and can't point to different objects. And that's the birth of symbols. A symbol is written with a preceding double-colon, to differentiate from variables, contants, classes, etc.
Code: Select all
:greet #a symbol is created
:greet = "moin" #syntax error, a symbol is immutable, it is not a variableThe symbol we created is now added to a table of all symbols used by Ruby. All of them are unique, :greet is only in there one time, no matter how often and under what circumstances we use it! You can check this table, by using the Symbol class's method.
Code: Select all
Symbol.all_symbolsIf you do that, you will notice that there are no rules as to how you name it, other than a preceding double-colon. In fact, you will find symbols like
:Class
:"core#set_method_alias"
:[]=
and even the name of the class Symbol, :Symbol
Now you might think, that symbols still are much like strings. Maybe not in how they are handled by Ruby, but in their behavior. And yes, that's not wrong. A symbol is an identifier, and as such needs a descriptive name, which is almost as if it were a string. And you can indeed convert a symbol to a string, and a string to a symbol.
Code: Select all
@moin = :greet.to_s #symbol was converted and is now the string object "greet"
@sym = @moin.to_sym #string converted to symbol object :greetI want to point out, that you should never use #to_sym, unless you know exactly what you are doing! Since symbols stay in memory, you can create a memory leak, when, for example, automatically creating symbols from a user inputted string over and over again (every different string will become an additional symbol, sitting in memory until the app/plugin is closed)!
But of course there are advantages. For once, there's only ever one symbol of the same label, which means a small lookup table where it can be found much quicker. Then they are immutable and therefore instantiating them is faster than instantiating strings. Execution time can be reduced as much as to a third of the strings' time.
The best use case for explaining symbols is Hashes, as they use key - value pairs. In other languages (they don't have symbols), you have to use strings as keys.
Code: Select all
names = {"forename" => "Jessica"}But we can use symbols and save time. Even better, symbols are so useful in hashes that there is a specific way of writing a key-value pair!
Code: Select all
names = { :forename => "Jessica" } #standard way of creating key-value pair
names = { forename: "Jessica" } #symbol specific way of creating key-value pairCompare the two ways of using a string or a symbol as key, and you see the clear advantage of identifiers over strings even just for the state of programming, aside from all optimizations! In the first example, you can't tell, which one is data, because both are strings. In the other examples it is obvious what is data and what is key. And even more, you can have nested Hashes, and use the same symbols, without any compromise. The same symbol in another Hash represents another type of data, just like a logo stands for fries or cheeseburger (or both), depending on your appetite.
In short, keep the strings for users of your app/plugin. Keep the symbols in your code, use them whenever you need an identifier and save time over strings.