11 ms·
Why Ruby Has Symbols
- luciusdomitius 4y agoI always thought Ruby has symbols because const String ACCOUNT_FUNDS_EXCEEDED = "ACCOUNT_FUNDS_EXCEEDED" is plain retarded :D
- OJFord 4y agoThat would be an odd way to do that in a language that doesn't have symbols too though.
- luciusdomitius 4y agoHave a look at 90% of corporate codebases.
- zem 4y agoit makes sure that any typos are caught at compile time, for one
- kungfufrog 4y agoTo be honest, while I've dabbled in Ruby, I've never understood the difference on an intuitive gut level like other foreign constructs unique to individual languages I've gone deep with. I don't know what it was about this article but I feel slightly more confused now. It jumps to bytecode before explaining how they're useful at the higher level of abstraction that is the developer. How do symbols help you think about and find solutions to problems and then implement those solutions? I.e., what is the facility they provide that is not present in some more traditional OOP language (say PHP?)
- crickcreek 4y agoA symbol is your string applied as the argument to an algorhtym that returns a deterministic interger. The interger is smaller and easier to sort and compare, making many common operations more efficient. --andrew
- TazeTSchnitzel 4y agoThe difference between symbols and strings only exists at the low level. At the high level they are virtually the same thing.
- eternityforest 4y agoIt seems like the difference(As far as I can tell without spending way more time with the article) is deduplication, which other languages already give you with strings. I think thr syntax is slightly nicer than quotes, but it's also more syntax, and there is a limit to how much you can have if you don't want code to look like Perl(Which Ruby is approaching). People are saying it can can be used for some kind of better compile time checking, would be interesting to see that as the main focus?
- klibertp 4y ago> deduplication, which other languages already give you with strings. Aren't Ruby's strings mutable? I'm not sure, but I seem to recall they were/are, in which case you can't really intern them. Python and Java have immutable strings, with the ability to optimize allocations being (probably?) one of the reasons. On the other hand, Symbols in Ruby seem to be immutable, which allows for their interning.
- noneeeed 4y agoIt's optional. By default strings are mutable, but you can freeze them individually, and you can set a directive that makes all string literals as immutable on a file-by-file basis.
- TazeTSchnitzel 4y agoYou can intern strings without preventing mutability using the copy-on-write principle. PHP does it.
- recursive 4y agoIf you copy before you write, you're not mutating, you're making a new thing. e.g. This pseudo code must stand for mutation to be present. a = ... b = a mutate(a) /* b now mirrors a */
- bjoli 4y agoIn scheme you usually use symbols instead of strings because the whole equality story is simpler and much faster. I once changed a tight loop in some code from dispatching on strings to dispatching on symbols and got a 15% speedup. Why? String equality is expensive. A symbol is basically a readable fixnum, where two similar symbols are always the same objects (errr... Don't quote me on that because it isn't strictly true). Most places where you can use symbols instead of strings you lose nothing and gain speed. I am not sure how it is in ruby though.
- berkes 4y agoIn Ruby `"Foo" == "Foo"` returns true. Whereas `"Foo".object_id == "Foo".object_id` returns false: They are not the same object, but report being equal. OTOH, symbols return true for both: they are the exact same object. It's not just about speed though: symbols are somewhat limited in what they can be made of. They follow the same limitations as methods and variables. So often symbols are used when dynamically calling methods or assigning variables. "Foo".public_send(:strip!) ¹. Which is slightly different from "Foo".public_send('strip!'). Not in outcome, but in calling. Because this is invalid syntax: "Foo".public_send(:one-two three) whereas this isn't: "Foo".public_send('one-two three'). Technically, I guess Ruby can have a method that is named "one-two three" but that would be really nasty to call. Symbols protect a lot against this. And therefore are used in this context a lot. ¹ The exclamation mark can be a part of a method and symbol in ruby. As can the question-mark and some other sugar-ish stuff like [].
- bjoli 4y agoRuby inherited that ?! convention fromm scheme. All mutating procedures end with ! and all predicates with !. Prefix notation has none of those pesky limitations if you can live with it :) Edit: oh. Scheme is painfully monomorphic. Equality for.strings is string=?. Equality for chars is char=?. Then there is object equality (eq? ...), eqv? ("Normally eq?") and equal? which is a generic equality predicate that works for all objects (including circular data structures). Eq? is the one you would use for symbols. Symbols are always (almost, at least) eq?. One string is only eq? To itself, but not a string containing the same content.
- jrochkind1 4y agoYeah, it's an interesting article about symbol implementation, but I think it's headline is wrong, it doesn't really discuss why ruby has symbols.
- dgb23 4y agoThey seem similar enough to Clojure keywords: Those are more focused and simpler than strings in say PHP. And they are first class, unlike members in Java. They are primarily first class names in your program. Think of them as distinct elements in a set, as opposed to arbitrary text to be transformed and parsed. If I give you a symbol (or keyword) then you know it is a name. If I give you a string, it could be anything really.
- tomc1985 4y agoThe article is an extremely needlessly complicated explanation of a relatively simple principle: symbols are stored once and referenced by pointer, strings are stored multiple times and not so easily compared. Ergo, for many use-cases symbols are faster and use less memory. Some of these use cases are little tokens like single words that are used in as values in function arguments, or a switch statement. On the other hand, storing the user's inputted name as a symbol whilst copying that data to your model object is probably not a good idea. Yes this explanation leaves out a lot of detail.
- ravi-delia 4y agoThey're largely a holdover from smalltalk and lisps, which use them as a sort of generalized token. I find them handy mostly because you can express things like slot names, enums, or keys in a way that's unambiguously not user provided. In Elixir for instance (which has Ruby's symbol syntax slapped on Erlang's atoms), you'll often report a failure with {:err, "error message"} so you can pattern match on it. In principle, with immutable strings, you could just have {"err", "error message"}, and it would work the same! But that's hard to distinguish from a list which happens to contain two strings. Of course, the only thing prohibiting :err from being part of the data is convention. But if you're just looking over the code, the atom stands out almost like a syntactic feature, so it's easier to hold to. Plus, since they're interned strings underneath, you can use them in macros to make things like schemas unambiguously, and convert them into migrations with very little magic. So that's it, nothing you couldn't do with interned strings, variable names, and enums, but all in one handy little first class datatype.
- matheusmoreira 4y agoString equality is linear time in the worst case. Symbol equality is constant time. Symbols are strings that act like numeric constants. They're extremely useful in APIs as arguments, return values and generalized tokens. They're also the obvious choice for hash table keys.
- ktpsns 4y agoLanguages with native symbol types are very helpful for... you name it: Symbolic programming https://en.wikipedia.org/wiki/Symbolic_programming https://en.wikipedia.org/wiki/Symbolic_programming In the context of computer algebra systems, which are much about manipulating abstract syntax trees, mathematical variables are usually represented as "symbols". Beyond that, this page gives databases as an example, which is in fact very nice. Beyond being fast and efficient, using symbols allows certain errors to be compile-time instead of runtime, where typos are only detected on an application level and not on a code level. This is where symbols can play out their advantage. Think a bit of ENUMs in other languages.
- smegsicle 4y agothe "you name it" idiom in english is usually used to mean "anything you want", as in "you pick it"- so your first sentence reads like "native symbols are useful for anything, because (as everyone knows) symbolic programming is useful for anything" i think the idiom you were going for was perhaps "you guessed it" or "you called it", as if poking fun at how, obviously, native symbols are helpful for symbolic programming, because it's the same word
- ktpsns 4y agoThanks for the tip! In fact I'm not a native speaker and this was some kind of "false friend" from the German "du sagst es" (="you say it") :-)
- charcircuit 4y agoHow does this article not mention LISP? Ruby has symbols because it was inspired by LISP which had symbols.
- klibertp 4y agoNo, Ruby has symbols because it was inspired by Smalltalk which has symbols. Of course, Lisp also has symbols, and it was one of the inspirations for Ruby (and Smalltalk), but the idea of representing message sends (ie. method calls) as symbol + arguments comes from Smalltalk.
- dmurray 4y agoIt's not a historical article tracing the lineage of the feature. Ruby took some things the designer liked from Lisp, from Smalltalk, from Perl, from other places. He liked symbols because they're good for performance (and compile-time correctness) at a low cognitive cost, and that's why Ruby has symbols.
- rkangel 4y agoElixir (and Erlang) have atoms which are exactly the same thing. They're useful in any dynamic programming language - in a static one, the equivalent is different values of an enum. Python notably doesn't, and as such you get functions that take arguments that are strings with special meaning, which I always found a bit clunky even before I discovered Ruby.
- tomn 4y agoI think Erlang has them for a more specific reason: in general it eschews all forms of data definition (records are just a fancy syntax for tuples with an atom at the start), which makes hot code reloading and transparent network communication simpler. Both cases would require some synchronization of data structures (across time or over the network), and with user-defined types this can get complicated, and atoms make the lack of user-defined types much more pleasant (and more performant than strings).
- quietbritishjim 4y agoPython has had enums [1] since Python 3.4 (2014). You can easily convert back and forth between enum values and their string and numeric values. I don't know anything about Ruby or Erlang so I don't know if that's really relevant, just your comment seems to imply it doesn't. [1] https://docs.python.org/3/library/enum.html https://docs.python.org/3/library/enum.html
- rkangel 4y agoYes, that's fair. I suppose I was describing my process of "discovery" and learning with Python which was significantly before 2014. Even now though, enums are not usually the normal way of doing things in public APIs, but that's presumably at least in part because of the history.
- nerdponx 4y agoAlso, unlike Ruby, string literals are usually interned in CPython (I think below a certain size), so they have at least some of the performance benefits of symbols in Ruby.
- dorianmariefr 4y agoSymbols are garbage collected now so it’s just shorter strings IMHO it’s a style choice
- barrkel 4y agoRuby doesn't have symbols because of AST or VM details. Ruby has symbols in all probability because Lisp and Smalltalk have symbols. It could get most of the same practical upside of symbols from interned strings - the important thing is being able to compare using pointer equality and look up hash tables without needing to walk a string. What symbols at the type level do is ensure that these string-like things have already been interned, that is, de-duplicated, when they hit lookup points like member access. But the implementation could do something very similar behind the scenes by setting a bit on interned string values. Besides, symbols aren't enough for the more advanced dynamic language optimization techniques like you see in V8.
- zimbatm 4y agoThat's a better explanation. There is a clear Smalltalk influence in Ruby, especially around the object-oriented aspects of the language. The best example is how the language doesn't call function, but sends a message to a method. And also how everything is an object. Matz talked quite a bit about the various other languages that influenced the design and Smalltalk and Lisp are part of that list (and Perl).
- chrisseaton 4y ago> It could get most of the same practical upside of symbols from interned strings I don't think so - unless you mean always interning all strings. The point of symbols is you can do a single address comparison. How can you do that if you could have two strings that are the same but have different addresses? There is also a down-side of symbols - they by definition always escape the compilation unit since they're interned!
- masklinn 4y ago> I don't think so - unless you mean always interning all strings. The point of symbols is you can do a single address comparison. How can you do that if you could have two strings that are the same but have different addresses? You intern all the literals (which includes lexical symbols) and are 99% if the way there.
- chrisseaton 4y agoMy opinion is that Ruby has symbols for strings that are static - part of the program - and normal strings for dynamic runtime data. Separating the two is useful semantically because it lets you differentiate between the two - and because these two kinds of string are better off being implemented and optimised in different ways. So it's both UX and practical mechanical sympathy.
- agalunar 4y ago> When AST is built, it is validated to make sure it makes sense (that’s called lexing) and converted it to the bytecode. I've never heard "lexing" used this way, and I believe it's simply incorrect. Lexing (tokenizing) precedes parsing (parse tree and then syntax tree construction). It isn't syntax tree validation. Or so I thought. Are there other examples (besides this article) of "lexing" also being used to mean something else?
- dmitrytsepelev 4y agoThank you for catching that! Not sure where my eyes were, feels like it's a remnant of another version of the sentence. Removed
- agalunar 4y agoNo problem! Same thing happens to me, where my eyes begin to gloss over things I've written (because I know what I mean to say). Thanks for the submission!
- carapace 4y agoBTW, a good technique for catching those kinds of mistakes is to read the piece out loud. Engaging more of your nervous system makes the visual elision easier to detect.
- Existenceblinks 4y agoI think, it's called "elaboration" (In Standard ML) .. into elaborative semantics (AST that makes sense for evaluation phrase).
- nine_k 4y ago> validated to make sure it makes sense Indeed, I always thought that it's called "syntax checking" :)
- avgcorrection 4y agoThis seems to be a dynamic language thing. Could statically typed languages have some uses for it? In Java one might use a lot of constant strings which are keys for property files. You can of course do some dynamic stuff in order to construct them but they are more likely to be used as constant strings. Would using a separate construct for that make optimization easier? Or wouldn’t it make a difference in practice?
- dmitrytsepelev 4y agoJava does use string interning for constants. It can help in any case when you have a lot of instances of the same string (and when you need to compare that strings)
- mc4ndr3 4y agoThey're like enums but without the risk of colliding across unrelated contexts. And because they're dynamically generated you can have runaway symbol generation that triggers a memory consumption problem. Newbies try to use strings as enums, because they don't understand enums. Symbols provide tradeoffs compared with both. This is what you get with a dynamic language where people try to overload functions with "I accept a scalar OR an array!!!"
- cestith 4y agoFrom the caller's perspective there's little difference between a procedure with variadic arguments and multiple dispatch. There's a lot of difference from the implementor's perspective, though. In Perl 5 you'd see a lot of ref() and wantarray() while in Raku you'd see different signatures for procedures with the same name.
- BurningFrog 4y agoWhen I started ruby, I thought symbols were weird and dumb. After a while, it was one of the things I liked the most. Maybe my favorite part is I didn't have to read and write so many damned " characters! "Converting" "text" "like" "this" :to :this :format, really helps me read it. I might be the weird guy on this. Wouldn't be the first time.
- arcticfox 4y agoI'm in the same boat, FWIW. I really did not understand the point at first, but I love symbols now. I only vaguely understand the point, even after reading this thread, but I like using them.
- pdkl95 4y agoWhile other comments have discussed the technical utility of symbols, I believe symbols can also be seen as useful syntactic sugar that helps communicate intent. Strings used for indexes. named args, and other structural purposes can be represented in a way that is visually distinct from strings used as text. The technical benefits are nice, but this type of ergonomic feature is why ruby has remained my favorite language for over a decade.
- berkes 4y agoI was on the same page, but now moving away from that. I more and more dislike how Ruby (arbitrarily) allows omitting brackets. but not always. Often making the code harder to read. What is the call-chain in this rspec magic: `expect(something).to be >= 1` (quick: where and how do you add a custom failure message). And while `attr_accessor :time, :date, :state` are really neat, I more and more dislike constructs like `validates :name, :login, :email, presence: true`. And prefer to write them explicit and unambiguous: `validates_presence_of(:name) etc`. Which is only a very slightly improvement over `validates_presence_of('name')`. And don't get me started on "saving time" by typing less characters or shorter lines of code: if this is what makes you Go To Market faster, there's something very wrong with your IDE, editor or typing skills. If anything, those short things have cost me time in Rails codebases living years and years.
- 4y ago
- Asmod4n 4y agoThe difference between comparing symbols and strings in ruby. https://twitter.com/chrisgseaton/status/1514603665801109508?s=21&t=7wjo8oes01uqBondhVzBoA https://twitter.com/chrisgseaton/status/1514603665801109508?...
- lamontcg 4y agoIn the early days, symbols weren't garbage collected, while strings were mutable, wasted memory and were slow. So there were tradeoffs. Now you can use frozen string literals and there's no benefit to symbols. Throwing "# frozen_string_literal: true" in the top of the memory benchmark script I get: Calculating ------------------------------------- strings 0.000 memsize ( 0.000 retained) 0.000 objects ( 0.000 retained) 0.000 strings ( 0.000 retained) symbols 0.000 memsize ( 0.000 retained) 0.000 objects ( 0.000 retained) 0.000 strings ( 0.000 retained) Comparison: strings: 0 allocated symbols: 0 allocated - same At this point with no practical difference between them STRINGS AND SYMBOLS BEING DIFFERENT ARE A MISTAKE. When you serialize to something like JSON you lose the distinction (the operation is singular and does not have an inverse transform) and you have to pick either symbols or strings to get back. On a long enough timescale this causes enormous confusion, and leads to the creation of hashes with indifferent access (which helps with the problem, but doesn't fix it). Ideally it would be good at this point to make symbols and frozen strings completely equivalent ("foo".freeze == :foo being true) but that would likely break too much existing code. The differentiation between strings and symbols though only causes code bugs (mostly biting the new and intermediate level programmers). It is just syntactical sugar with a footgun. Designing a language from scratch these days, it should have immutable strings by default from the start and should not introduce symbols, unless they are purely syntactic sugar around creating an immutable string.
- berkes 4y agoI both like and hate how Rust has `String` and `&str`. Constant juggling between the two (which really is a sign I'm not doing it right). Yet knowing and using the difference is important and powerful. I somewhat miss this when I go back to Ruby, but then realize that symbols often can be used for `&str`. Often. Not always.
- chrisseaton 4y ago> At this point with no practical difference between them Gah no! This isn't true! Even if you turn on frozen string literals comparing two strings is slower because they have to test for a non-frozen and non-interned string also happening to be the same. https://twitter.com/ChrisGSeaton/status/1514603665801109508 https://twitter.com/ChrisGSeaton/status/1514603665801109508 There's a pointer comparison, but behind it is on the failure side is a full-byte-comparison. Atrocious for cache even if the strings are tiny. If they aren't you're checking every byte!