Crazy thought -- Rubyish type conversion?

[email protected] wrote:

It sounds like you’re talking about class, rather than type, or
(since, as you say, to_x doesn’t actually have to return an X) just a
kind of convenience method-call layer. I’d rather see the methods
spelled out.

No, I don’t mean class – I think of “type” as the context of an object,
one that cannot always be deducted by it’s methods:

phone.call
method.call

these two #call methods are in different contexts – I think of the
#to_* methods as a way to determine that context. If I call #to_a on an
object, I expect the result to be an array – I could’ve just called #[]
and what not on the original object, but that would require that the
object only used #[] the same way an array does. By using the #to_*
methods, objects can be in several different contexts. At least that’s
how I see it.

Daniel

Built-in adaption routes that Ruby already uses in its C code.

adaption :to => Symbol, :via => :to_sym
adaption :to => String, :via => :to_str
adaption :to => Array, :via => :to_ary
adaption :to => Integer, :via => :to_int

That’s interesting. Is this data accessable via pure Ruby code? I was
playing with a lib that provided these like so:

class Symbol
def self.coercer ; :to_sym ; end
def self.convertor ; :intern ; end
end

class Array
def self.coercer ; :to_ary ; end
def self.convertor ; :to_a ; end
end

class Hash
def self.coercer ; :to_hash ; end
def self.convertor ; :to_h ; end
end

class String
def self.coercer ; :to_str ; end
def self.convertor ; :to_s ; end
end

Etc. that way any class could provide the information.

T.

Florian wrote:

I have implemented this in ruby-contract. Please see

http://ruby-contract.rubyforge.org/

Interesting library. Would like to see a tutorial on how to use it.

T.

[email protected] wrote:

def self.coercer ; :to_sym ; end
def self.convertor ; :intern ; end

end

Etc. that way any class could provide the information.

ruby-contract’s API for providing adaptions is pretty simple. adaption()
also supports more powerful routes. See
http://ruby-contract.rubyforge.org/doc/classes/Kernel.html#M000019

You can also see the unit tests at
http://ruby-contract.rubyforge.org/release/ruby-contract-0.1.1/test/tc_integration.rb
for sample usage.

You can get a list of all adaption routes from the outside by doing
Contract.adaptions, but I don’t see a case where that would be
necessary. For that reason the method is undocumented.

[email protected] wrote:

I have implemented this in ruby-contract. Please see

http://ruby-contract.rubyforge.org/

Interesting library. Would like to see a tutorial on how to use it.

Please have a look at the test cases and the documentation for now.

[email protected] wrote:

That’s pretty close really. If you can do this (w/o Binding.of_caller
though b/c it’s too fragile).

def foo(bar, baz, bur)
signature String, Integer, Symbol
# something
end

Disagreed. I think the signature information should be available before
you call the method.

Please note that ruby-contract also adds a signature() method to the
Method and UnboundMethod classes. That would not be possible when the
signature is inside the actual method. I also think that you can’t get
the above syntax to work because argument order information will be
lost.

And I don’t think explicit conversion belongs into a type library. It’s
something that ought to be explicitly done by the user IMHO.

def foo(bar, baz, bur)
# something
end
signature(:foo, String, Integer, Symbol)

That’s pretty close really. If you can do this (w/o Binding.of_caller
though b/c it’s too fragile).

def foo(bar, baz, bur)
signature String, Integer, Symbol
# something
end

Hmm… how about something like this:

def foo(bar, baz, bur)
signiture { coerce String, Integer, Symbol }
# something
end

The block would return a spical Signiture object that can be used by
apply the set of conditions on the arguments. In this case the args are
coerced, alternatively they could be coverted (to_s rather then to_str)
or just perhps amde to throw an argument error if not matching, etc.
I’m sure there’s a variety of argument conditions that can be defined.
The use of the block allows us to get hold of the methods binding w/o
Binding.of_caller.

T.

T.

Hi –

On Sun, 28 May 2006, Daniel S. wrote:

method.call

these two #call methods are in different contexts – I think of the #to_*
methods as a way to determine that context.

But that’s a different matter from either the class or the type of
phone and/or method.

If I call #to_a on an object, I
expect the result to be an array – I could’ve just called #[] and what not
on the original object, but that would require that the object only used #[]
the same way an array does. By using the #to_* methods, objects can be in
several different contexts. At least that’s how I see it.

Don’t forget, though, the “to” in to_* really means a different
object. It’s not the same object behaving differently.

The built-in to_* methods are very class-specific, and in a sense are
not the best advertising for duck typing, though they are very
convenient and they’re closely tied in with the most basic built-in
classes. I think I’d expect any method named to_* to fit into that
pattern. In a sense, it sounds like you’re describing modular
extension:

obj.extend(SomeModule).some_method

or something like that, where an object’s behaviors can be modified on
the fly.

David

[email protected] wrote:

No, I don’t mean class – I think of “type” as the context of an
object, one that cannot always be deducted by it’s methods:

phone.call
method.call

these two #call methods are in different contexts – I think of the
#to_* methods as a way to determine that context.

But that’s a different matter from either the class or the type of
phone and/or method.

Is behavior not relevant to type?

Don’t forget, though, the “to” in to_* really means a different
object. It’s not the same object behaving differently.

str = “foo”
str.object_id #=> 23456248224800
str.to_str.object_id #=> 23456248224800

The built-in to_* methods are very class-specific, and in a sense are
not the best advertising for duck typing, though they are very
convenient and they’re closely tied in with the most basic built-in
classes.

I don’t think they have to be tied in with the classes, and I think we
should try to avoid getting type mixed up with class. When I call #to_x,
I expect to get an X type, meaning that I can call the methods defined
by X on the object, and the object responds in an expected manner. I
don’t care if the object actually is an instance of X, just as long as
is keeps its side of the deal and responds to the methods I throw at it.

I can see why some of the low-level methods might actually need an
actual String or Numeric instance, but for high-level code I just don’t
see the point – it’s not like knowing the class means you can be any
more sure that no-one has tampered with it.

I think I’d expect any method named to_* to fit into that
pattern. In a sense, it sounds like you’re describing modular
extension:

obj.extend(SomeModule).some_method

or something like that, where an object’s behaviors can be modified on
the fly.

Well, yes, an object’s behavior can be modified on the fly. That’s one
of the selling points of Ruby. But by requiring that classes must
inherit from some core classes, or that methods be defined on the core
classes themselves, we restrict the flexibility of Ruby.

Daniel

Hi –

On Mon, 29 May 2006, Daniel S. wrote:

phone and/or method.

Is behavior not relevant to type?

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?

Don’t forget, though, the “to” in to_* really means a different
object. It’s not the same object behaving differently.

str = “foo”
str.object_id #=> 23456248224800
str.to_str.object_id #=> 23456248224800

Well, at least it’s not behaving differently :slight_smile:

the deal and responds to the methods I throw at it.

I can see why some of the low-level methods might actually need an actual
String or Numeric instance, but for high-level code I just don’t see the
point – it’s not like knowing the class means you can be any more sure that
no-one has tampered with it.

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_*.

David

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 :slight_smile:

Okay, bad example :stuck_out_tongue:

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