I have a class which exposes a string value and an int value (a command output and exit code respectively). In addition to exposing them through to_s
and to_i
, I'm also using to_str
and to_int
, like so:
class Status
def to_s
@output
end
alias :to_str :to_s
def to_i
@status.exitstatus
end
alias :to_int :to_i
end
My idea behind this is to be able to use this object in as many situations as possible. having it coerceable to a string or int increases that usability. For instance, I can concatenate the object with a string:
a_string = "Output was: " + results
(I wanted to use this as an example of int coercion, but Fixnum.+ doesn't like it, so it doesn't actually work:)
an_int = 1 + results
Everything I've read so far has said that this is probably a "bad" thing to do. The common theme goes like this: "Use to_s
/to_i
when your object can be represented as a string/int, but to_str
/to_int
only if your object is fundamentally a string/int".
There's no question that my class is not "fundamentally" a string or an int. However I have some problems with this rule:
- It makes my class less flexible/usable. For example: I couldn't use String.+ to concatenate the Status output with the other string if I didn't have Status.to_str.
- It seems to violate the spirit of duck-typing. The user of an object (ie: a method that gets it as a parameter) shouldn't care what that object is, it should only care what it can do. (In this case, "do" means "can be represented as a string/int".)
- The arguments for "is fundamentally a string/int" are pretty fuzzy to me. For example, you'll see that
Float.to_int
is mentioned a lot. The story goes that since a floating-point number always has an integer component,to_int
is a valid method. However, I think this is spurious: a Float is not an integer (as it has a non-integer component) and so trying to equate their "typeness" doesn't make much sense. You can legitimately convert a Float to an integer (through truncation), but then I can say that I can convert my Status to an integer as well (by "truncating" all of the non-exit-code information).
So, my question is: is there any real (ie: practical) harm in implementing to_str
and to_int
?
Update: Jörg W Mittag gave an example that made me thing of something. To rephrase the question: is there really a need to have to_str
/to_int
when you already have to_s
/to_i
? (Besides the fact that particular methods are already expecting to_str
over to_s
)
For instance, in Jörg's Array.join example, the array members are converted via to_s while the separator is converted via to_str. But is this really necessary? If Array.join called separator.to_s instead, then you could successfully pass many more objects to it (ex: Integers, Symbols, etc) and gain that much more flexibility. Does Ruby benefit from having this separation?