user = { name: "Alice", age: 30 }
puts user[:name] # Alice
puts user["name"] # nil
I'm 100% convinced that every Ruby developer has at least once made a bug where they tried to access a hash entry using a symbol, where the key was actually a string or vice-versa.
It would be great if Ruby would finally have immutable strings by default and, at that point, it would be possible to make symbols be strings.
This would prevent any such user[:name] vs user["name"] bugs while not breaking any other functionality. And also keeping the memory "optimized" by reusing a single immutable string.
Symbols are special cased pseudo-strings for langages which have either extremely inefficient strings (erlang) or mutable ones (ruby). That’s about the extent of it.
Python or Java don’t really have a use for them because their strings are immutable, and likely interned when metaprogramming is relevant (e.g. method or attribute names).
Interning is important for symbols that are involved in I/O: being printed and read, so that two or more occurrences of the same symbol in print will all read to the same object, and so there is print-read consistency: we can print an interned symbol, and then read the printed symbol to obtain the same object.
Symbols are useful without this also. Symbolic processing that doesn't round trip symbols to a printed notation and back doesn't require interned symbols.
Symbols have a name, but are not that name.
They also have various properties, which depends on the Lisp dialect.
Classical Lisp dialects, like MacCarthy's original, endow each symbol with a property list. Another classical property is the "value cell".
In Common Lisp lists have a home package retrieved by the function symbol-package.
A variable being globally proclaimed special can be implemented as a property of the symbol.
Symbols are interned in packages, not globally, so two symbols can be interned, yet have the same name: mypackage:let and cl:let both have the name "LET", but different home packages.
Uninterned symbols with the same name can be readily made: just call (make-symbol "FOO") twice and you get two symbols named "FOO", which print as #:FOO.
The #: notation means symbol with no home package, used as a proxy for "uninterned", though a perverse situation can be contrived whereby a symbol has no home package (and so prints with the #: notation), yet is interned into a package.
Introduce a FOO symbol in the keyword package:
[1]> :foo
:FOO
Now import it into the CL-USER package:
[2]> (import :foo :cl-user)
T
Verify that cl-user::foo is actually the keyword symbol :foo:
[3]> 'cl-user::foo
:FOO
Now, unintern :foo from the keyword package, leaving it homeless:
[4]> (unintern :foo :keyword)
T
Let's print it, accessing it via the cl-user package where it is still interned by import:
[5]> 'cl-user::foo
#:FOO
There is quite a bit to this symbol stuff than just "interned strings".
Symbols are simply not strings objects; they have strings as a name.
Well, it's a string that will guarantee unique allocations (two identical strings are guaranteed to be allocated at the same address), which makes equality checks super fast (compare pointers directly). But pretty much just a string nonetheless...
The article is a bit confusing, but I like the concept behind symbols: Basically a way to distinguish identifier names (chosen by the programmer) from user-facing strings.
The distinction between is one I've mentally adopted in other languages like Python as well. For personal projects I like to use single quotes for 'symbols' and double quotes for "strings", e.g.:
I'm a ruby developer, would never dream of switching to another language as my bread and butter - but my language design hot take for ruby is that symbols were a mistake and unnecessary and the language would have been better off just using frozen (immutable) string literals for everything except for the syntax of keyword arguments.
Unfortunately we can't change it now, but generally I just don't use symbols anymore unless I absolutely have to.
10 comments
[ 0.24 ms ] story [ 31.8 ms ] thread> Symbols aren’t interchangeable though.
I'm 100% convinced that every Ruby developer has at least once made a bug where they tried to access a hash entry using a symbol, where the key was actually a string or vice-versa.It would be great if Ruby would finally have immutable strings by default and, at that point, it would be possible to make symbols be strings. This would prevent any such user[:name] vs user["name"] bugs while not breaking any other functionality. And also keeping the memory "optimized" by reusing a single immutable string.
Symbols are special cased pseudo-strings for langages which have either extremely inefficient strings (erlang) or mutable ones (ruby). That’s about the extent of it.
Python or Java don’t really have a use for them because their strings are immutable, and likely interned when metaprogramming is relevant (e.g. method or attribute names).
Interning is important for symbols that are involved in I/O: being printed and read, so that two or more occurrences of the same symbol in print will all read to the same object, and so there is print-read consistency: we can print an interned symbol, and then read the printed symbol to obtain the same object.
Symbols are useful without this also. Symbolic processing that doesn't round trip symbols to a printed notation and back doesn't require interned symbols.
Symbols have a name, but are not that name.
They also have various properties, which depends on the Lisp dialect.
Classical Lisp dialects, like MacCarthy's original, endow each symbol with a property list. Another classical property is the "value cell".
In Common Lisp lists have a home package retrieved by the function symbol-package.
A variable being globally proclaimed special can be implemented as a property of the symbol.
Symbols are interned in packages, not globally, so two symbols can be interned, yet have the same name: mypackage:let and cl:let both have the name "LET", but different home packages.
Uninterned symbols with the same name can be readily made: just call (make-symbol "FOO") twice and you get two symbols named "FOO", which print as #:FOO.
The #: notation means symbol with no home package, used as a proxy for "uninterned", though a perverse situation can be contrived whereby a symbol has no home package (and so prints with the #: notation), yet is interned into a package.
Introduce a FOO symbol in the keyword package:
Now import it into the CL-USER package: Verify that cl-user::foo is actually the keyword symbol :foo: Now, unintern :foo from the keyword package, leaving it homeless: Let's print it, accessing it via the cl-user package where it is still interned by import: There is quite a bit to this symbol stuff than just "interned strings".Symbols are simply not strings objects; they have strings as a name.
A Symbol is really just a string!
Well, it's a string that will guarantee unique allocations (two identical strings are guaranteed to be allocated at the same address), which makes equality checks super fast (compare pointers directly). But pretty much just a string nonetheless...
This has been true forever.
The distinction between is one I've mentally adopted in other languages like Python as well. For personal projects I like to use single quotes for 'symbols' and double quotes for "strings", e.g.:
Does anyone else do something similar?A json api, should it use symbols or strings?
Json has string keys, but the api keys are chosen by the programmer.
I can't remember which edgecase,but there are another bunch I found that are tricky.
Unfortunately we can't change it now, but generally I just don't use symbols anymore unless I absolutely have to.