views:

105

answers:

2

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:

  1. 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.
  2. 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".)
  3. 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?

+2  A: 

The spirit of duck-typing certainly wouldn't have someone look up the source code for the Status object to figure out what is getting returned.

Personally I think you should expose the text results and the exit status through two instance methods:

class Status

  ...

  def message
    @output
  end

  def exit_status
    @status.exitstatus
  end

end

And then use it as follows

a_string = "Output was: " + results.message
a_int    = 1 + results.exit_status

Which immediately makes sense to anyone reading the code, IMHO.

gpaul
I did that in the actual code (see the linked class), but omitted those methods for brevity in the example code I posted here.
Craig Walker
+2  A: 
Jörg W Mittag
Agreed on string interpolation being a better way in practice; I used string concat simply as an example of something that required to_str. (In fact, in real coding, I avoid concatenation simply because it *doesn't* work with arbitrary objects).
Craig Walker