Hi David,
[email protected] wrote:
I think I’m not understanding what you mean by the context of an
object. Your example involves two different objects (I assume), so
I’m not clear on which object’s context/type you’re referring to. Or
do you mean the context in which a given method name appears?
“Context” here is the knowledge that certain methods are not only
defined, but that they have certain semantics – i.e. Can#open and
Connection#open have the same name, but they do different things. If an
object were both a can and a connection (but not necessarily instances
of Can or Connection), it could define #to_can and #to_conn methods.
Then any method requiring a can could call #to_can on the argument, and
expect the #open method on the object returned to behave as expected.
#to_can could of course return a Can instance, but I just don’t see the
reason why that should be the only option; that would mean that all cans
must inherit from Can, which in this case of course is reasonable, but
it isn’t always. If we require that the object be of a certain class, we
make inheritance about type, and not just behavior. That’s why I think
we should (where it’s not absolutely necessary) disregard the class of
the return value of the #to_* methods, and instead just expect it to
behave properly.
str = “foo”
str.object_id #=> 23456248224800
str.to_str.object_id #=> 23456248224800
Well, at least it’s not behaving differently
Okay, bad example
What I mean is: there’s already a convention of using to_* for these
class-specific conversion methods, so if you want to create a system
of true type (as opposed to class) conversions, to_* might be a
misleading name.
In other words, I’m not suggesting that how the core to_* methods work
should be a model for how everything should work, but only that
anything that doesn’t work the way they work maybe shouldn’t be called
to_*.
You do have a very good point there – the #to_* methods have been used
in a rather non-quacky way. Perhaps #as_*?
Basically, I think of objects as pieces of information. This information
can have several different representations, but the type should always
be about the information, not the representation.
class DollarAmount
def initialize(amount)
@amount = amount.to_f
end
def to_str
“$#{@amount}”
end
def to_f
@amount
end
end
Here the information is both the amount of dough, and the fact that
it’s in dollars, and not yen. There a two other representations of the
instances of the class, a string and a float – these representations
contain less information than the original object would, but they can be
manipulated in ways a DollarAmount can’t, unless you define lots and
lots of methods yourself.
The reason I think we should use the #to_* methods on received arguments
(I’m not sure whether or not you agree that it’s a good idea) is that I
believe it to be the responsibility of a method to decide which
representation of an argument it wants. One method may convert the
DollarAmount to euro, so it needs the numeric representation – but it
should be smart enough to be able to receive a DollarAmount instance,
and get the relevant representation itself.
def dollar_to_euro(dollars)
dollars.to_f * D2E
end
The reason I think we should disregard the class of the objects returned
by #to_* is that it decreases the flexibility of Ruby – sometimes you
want to create a completely different implementation of a “type”, like,
say, a hash, but with the exact same interface. An OrderedHash, perhaps.
Should the author then be forced to have his class inherit from Hash,
when really the implementation is completely different? #to_hash should
return a hash, but must it be a Hash?
Daniel