A Ruby WTF

by Gregory Brown

While working on Prawn, I ran into this (not-so) fun little gotcha:


>> 1.to_sym
=> nil
>> 101241.to_sym
=> nil


Anyone cool enough to tell me what this feature is all about?

Update:

I guess it isn't totally clear what I was asking here. I'm not actually trying to convert integers to Symbols. In fact, my specs were failing because I expected some_number.respond_to?(:to_sym) to be false!

As it turns out, Fixnum#to_sym does have a purpose, but it is quite different than something like String#to_sym.


>> :foo.to_i
=> 14369
>> 14369.to_sym
=> :foo


I knew about the existence of #id2name and it didn't surprise me much, given the way Symbol objects are implemented. Still, for the folks who keep reminding me about what Symbol objects are, please save your comments because that's not the point here.

The point is that when I see "foo", I can easily say. Oh... "foo".to_sym will give me :foo.
When I see [16393, 16401, 16409], I don't think "Gee... that must be: [:cat, :monkey, :tomato], I just need to map it with to_sym".

So what this boils down to is an API clarity thing. Even if a Symbol is closely bound to an integer implementation-wise, I think it's a bit of a flaw to assume that to be important conceptually. Among our readers, has anyone used Fixnum#to_sym in real code? I'd be very interested in seeing a common use case for this feature. If there isn't one, maybe a less ambiguous name, such as id2sym might be more appropriate.

What's more, even with the existing name, it'd be nice if some_number_that_has_no_symbol.to_sym would blow up with an error rather than return nil.

Sorry for the earlier confusion, hopefully this update clarifies that I was talking about a design peculiarity., and not some burning desire to get myself back a :1.


14 Comments

Jeffrey Hardy
2008-04-28 09:04:43
I'm not sure what you were expecting to happen. :1 is not a valid symbol.
Gregory Brown
2008-04-28 09:09:24
@Jeffrey


Something like this:



>> Object.new.to_sym
NoMethodError: undefined method `to_sym' for #
from (irb):1
>> [].to_sym
NoMethodError: undefined method `to_sym' for []:Array
from (irb):2
>> {}.to_sym
NoMethodError: undefined method `to_sym' for {}:Hash
from (irb):3
>> 1.01.to_sym
NoMethodError: undefined method `to_sym' for 1.01:Float
from (irb):4


Silence is bad! I was using a gentle check like:



raise unless obj.respond_to?(:to_sym)


Fixnum's bogus implementation pokes a hole in that, and on top of that, I'd never expect to_sym to return anything *but* a Symbol.

Jeffrey Hardy
2008-04-28 09:17:17
Well, since Fixnum#to_sym uses id2name, the nil return value makes sense. 1.chr doesn't correspond to a defined symbol (neither does 101231). 37 does (37.chr #=> "%"), so 37.to_sym # => :"%".
Jeff Mesnil
2008-04-28 09:20:11
Fixnum#to_sym is unintuitive: http://www.ruby-doc.org/core/classes/Fixnum.html#M001071


It does not return a symbol corresponding to the number, it return "the symbol whose integer value is fix"
If you don't have a symbol whose value corresponds to 1 or 101241, it returns nil

brian doll
2008-04-28 09:20:59
A symbol has lots of fun definitions, but one that may be helpful here:
A Ruby symbol is a thing that has both a number (integer) representation and a string representation.


When a string receives a to_sym message, a symbol is created, giving a named reference to that string object, along with an integer (it's "id" in the symbol table, i'd guess)


When you call to_sym on an integer, what would it's string representation be? int.to_s? I don't think that would be of much value, and probably isn't what a programmer would expect (or want).


To that end, I'd guess that to_sym has some rules baked in to only coerce certain types into string representation, and for some (like Integer) it refuses to do so, returning nil.


It is a little curious why the method exists on Integer though. It does not exist on Float, for example.

Gregory Brown
2008-04-28 09:23:33
I see:



>> :foo.to_i.to_sym
=> :foo


Hmm... I don't think I've ever had a need for this feature. I've always used to_sym to convert a string into a symbol. Is there any practical use for this?


If anything, I'd prefer to see it renamed to id2sym, as it seems to be a rather rare use case to be changing a Fixnum back into the symbol that references it. Maybe I'm missing some exciting use case though...

Sean Bryant
2008-04-28 10:14:45
A ruby symbol cannot begin with a number directly, you must quote the symbol for it work.


So effectively 1.to_sym is useless. You can do :"1234" and be fine. The point is that a symbol must start with a letter.


An error will be raised if you just tried to create a symbol. I'm assuming to_sym returns nil when an object cannot be converted to a symbol.


:1
SyntaxError: compile error
(irb):4: syntax error, unexpected tINTEGER, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END
from (irb):4
from :0


And here's the quoted integer as a symbol:
irb(main):006:0> :"1"
=> :"1"
irb(main):007:0> :"1".class
=> Symbol

Christian Neukirchen
2008-04-28 14:34:09
This is for backwards-compatibility, because Ruby 1.4 didn't have Symbols (they were Fixnums).
Gregory Brown
2008-04-28 14:36:37
Chris2 to the rescue! Thanks for clearing this up.


Okay, now let's ask ourselves. How many Ruby 1.4 systems are still in production? :)

Maciej
2008-04-29 14:25:00
Gregory, you have probably too much time for writing such articles. Why don't you write to_s instead of to_sym and compare it to ["cat", "monkey", "tomato"].
Good developer is not the one who is good at solving problems, but the one who does not create them ;)
Gregory Brown
2008-04-29 15:10:45
@MacieJ,


Great cubicle mantra, but Ruby is a language open to criticism and change.

Mark Thomas
2008-05-16 06:35:52
Both of these are going away in 1.9.
Gregory Brown
2008-05-16 07:29:04
@Mark


Is this a scheduled change for 1.9.1 because I tested against 1.9.0 before posting this.



>> RUBY_VERSION
=> "1.9.0"
>> :foo.to_i
=> 15328
>> 1.to_sym
=> nil
markus hartenberger
2008-05-21 04:45:13
actually, i never used that at all in reallife code