I’d like to start the discussion about the future of Symbols in this topic.
101: symbols came to Crystal from Ruby and the look like :foo
or :"foo.bar"
. Basically, a symbol is a string “baked” in the binary. Every symbol has its own internal number representation. For example, when you run :foo == :foo
, it’s (roughly) compiled as n == n
, where n
is the :foo
's internal number. Obviously, comparing numbers is much more efficient than comparing strings, therefore Symbols are great. Aren’t they?
In Ruby symbols can be created in runtime (AFAIK), and all symbols in a Crystal program are known at the startup. Thus said, there are not that much use-cases left for the symbols in real-world Crystal applications, in my opinion.
Most of Symbol usages in the wild can be replaced with Enums. Hash and Named Tuple values can be accessed with String keys.
If you ask me, I see a good use for Symbols Symbol Literals in anything related to compilation-time checks and autocasting Enums.
Directly related: #6736
Afterlife
If Symbols got removed from the runtime and only Symbol Literals left in compilation time, we’ll be able to get the following things working (potentially):
Native autocasting Enums
Related: #6645
enum MyEnum
Foo
Bar
end
def foo(bar : MyEnum)
end
No more no overload matches foo with Symbol
errors on misspelling (e.g. foo(:baz)
Case with Enums
Related issue: #6592
case my_enum
when :foo
else
puts "Not Foo"
end
Would raise on misspelled enum (e.g. when :baz
) as well
Symbol Literals arguments in compilation time
That’s a game changer for me. In ORM DSL design, it’s intuitive to have Query methods like #where(id: 42)
, which check the arguments against a model’s instance variables during the compilation. And it works now with def where(**values : **T) forall T
(i.e. splatted NamedTuple argument). However, it does not work with splatted Tuple argument:
def select(*columns : *T) forall T
{% pp T %} # => {Symbol, Symbol}
end
query.select(:id, :name)
What I expect is:
{% pp T %} # => {:id, :name} (pair of SymbolLiterals)
It should also work with single free arguments in the same way:
def order_by(column : T, order : Order = :desc) forall T
{% pp T %} # => :id (SymbolLiteral)
end
query.order_by(:id, :desc)
There might be more use-cases, the post will likely to be updated. Thank you!