Forum: Ruby Crazy thought -- Rubyish type conversion?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 10:39
(Received via mailing list)
Now, I'm sure you'll all hate this, but I just had to post it.

What if optional "type hinting", or, more accurately, "type conversion",
was added to the method definition syntax?

   def foo(str bar, i baz, sym bur)
     # something
   end

would then be the same as

   def foo(arg1, arg2, arg3)
     bar, baz, bur = arg1.to_str, arg2.to_i, arg3.to_sym
   end

I know it would save me a lot of typing -- plus, it's still dynamic; it
wouldn't (necessarily) require the return values of the #to_* calls to
be instances of a certain class.

I know that some of you prefer to just call methods on the received
arguments, and not use the #to_* methods first to convert them to a
certain "type", but this proposal/idea wouldn't in any way make that
more difficult than it is today.

Well, it's just an idea.

(putting on my flame-proof suit)


Daniel
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 11:06
(Received via mailing list)
I even got an implementation, although it obviously differs a lot from
the proposed syntax.

   class Module
     alias_method :__define_method__, :define_method
     def define_method(name, *types, &block)
       if types.length > 0
         __define_method__(name) do |*args|
           args.each_index do |i|
             unless types[i].nil?
               args[i] = args[i].send("to_#{types[i]}")
             end
           end
           block.call(*args)
         end
       else
         __define_method__(name, &block)
       end
     end
   end

   class Test
     define_method(:foo, :sym, nil, :s) do |a, b, c|
       p a, b, c
     end
   end

   Test.new.foo("bar", 5, 6)   #=> :bar, 5, "6"


Cheers,
Daniel
05be5d6610e2c3f1780aa0e39e902e93?d=identicon&s=25 Farrel Lifson (Guest)
on 2006-05-28 11:13
(Received via mailing list)
It will make catching exception on type 'conversion' a bit hard. How
would you handle:

irb(main):001:0> 1.05.to_sym
NoMethodError: undefined method `to_sym' for 1.05:Float
        from (irb):1

Farrel
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 11:22
(Received via mailing list)
Farrel Lifson wrote:
> It will make catching exception on type 'conversion' a bit hard. How
> would you handle:
>
> irb(main):001:0> 1.05.to_sym
> NoMethodError: undefined method `to_sym' for 1.05:Float

I'm not sure where you're getting at -- there should be raised either a
NoMethodError or an ArgumentError. I'm not sure what's best, but I can't
see what the big deal is.

One thing that might need some though is whether a rescue clause on the
method definition should rescue exceptions raised when an argument
doesn't respond to the conversion method, i.e.

   def test(str foo)
     # blah
   rescue ArgumentError  # or NoMethodError
     puts "dude, you gotta give it a string..."
   end


Daniel
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-28 11:28
(Received via mailing list)
Just playing out loud....

  def foo(bar, baz, bur) % i, str, sym
     # something
   end

Can this be done already?

  def foo(bar, baz, bur)
     argsto :i, :str, :sym
     # something
   end

Otherwise

  def foo(*args) # bar, baz, bur
     args.mapto :i, :str, :sym
     # something
   end

T.
A9b6a93b860020caf9d2d1d58c32478f?d=identicon&s=25 Ross Bamford (Guest)
on 2006-05-28 12:29
(Received via mailing list)
On Sun, 2006-05-28 at 18:25 +0900, transfire@gmail.com wrote:
> Can this be done already?
>
>   def foo(bar, baz, bur)
>      argsto :i, :str, :sym
>      # something
>    end
>

Here's a naive implementation:

require 'facet/binding'

def argsto(*syms)
  Binding.of_caller do |b|
    lvs = b.local_variables
    lvs.each do |lv|
      break if syms.empty?
      type = syms.shift.to_s
      begin
        if type =~ /^[A-Z]/
          b[lv] = send(type, b[lv])
        else
          type = "to_#{type}"
          b[lv] = b[lv].send(type)
        end
      rescue NameError
        raise ArgumentError,"No coercion #{type} for #{b[lv].inspect}",
          caller[3..-1]
      rescue ArgumentError
        raise ArgumentError, $!.message, caller[3..-1]
      end
    end
  end
end

If you really need this kind of thing, it seems to work:

def ameth(foo, bar, baz, &blk)
  argsto(:Integer, :String, :sym)
  blk.call(foo, bar, baz)
end

ameth('10',100,'puts') { |*args| p args }
# => [10, "100", :puts]

The added bonus you don't get if you just use to_i and and so on:

ameth('10a',100,'puts') { |*args| p args }
-:28:in `ameth': invalid value for Integer: "10a" (ArgumentError)
        from -:32

It's not particularly smart or fast, though.
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 12:35
(Received via mailing list)
transfire@gmail.com wrote:
>   def foo(bar, baz, bur)
>      argsto :i, :str, :sym
>      # something
>    end

This seems like the most easily achievable way, although I'd still like
to have the types in the argument list.


Daniel
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 12:38
(Received via mailing list)
Ross Bamford wrote:
>
>           b[lv] = send(type, b[lv])
>     end
> ameth('10',100,'puts') { |*args| p args }
> # => [10, "100", :puts]
>
> The added bonus you don't get if you just use to_i and and so on:
>
> ameth('10a',100,'puts') { |*args| p args }
> -:28:in `ameth': invalid value for Integer: "10a" (ArgumentError)
>         from -:32
>
> It's not particularly smart or fast, though.

That's very interesting. Would it be better if class objects were given
instead of class names? Otherwise it could be tedious to convert to
A::B::BadgerMilk :)


Daniel
A9b6a93b860020caf9d2d1d58c32478f?d=identicon&s=25 Ross Bamford (Guest)
on 2006-05-28 12:48
(Received via mailing list)
On Sun, 2006-05-28 at 19:37 +0900, Daniel Schierbeck wrote:
> > Here's a naive implementation:
> >
> That's very interesting. Would it be better if class objects were given
> instead of class names? Otherwise it could be tedious to convert to
> A::B::BadgerMilk :)

Good point :) The way it's written, you can do that already, since the
'syms' are all to_s'd anyway.

$intclz = Integer		# just for example

def ameth(foo, bar, baz, &blk)
  argsto($intclz, String, :sym)
  blk.call(foo, bar, baz)
end

ameth('10',100,'puts') { |*args| p args }
# => [10, "100", :puts]
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 13:00
(Received via mailing list)
Ross Bamford wrote:
> def ameth(foo, bar, baz, &blk)
>   argsto($intclz, String, :sym)
>   blk.call(foo, bar, baz)
> end

I don't think that'll work with "nested" constants, i.e. A::B::C. I
think it may complicate things too much to allow classes/class names --
besides, it should be the arguments, not the classes, that should handle
the conversion.
E0ed615bd6632dd23165e045e3c1df09?d=identicon&s=25 Florian GroÃ? (Guest)
on 2006-05-28 13:06
(Received via mailing list)
Daniel Schierbeck wrote:

>     bar, baz, bur = arg1.to_str, arg2.to_i, arg3.to_sym
>   end

I have implemented this in ruby-contract. Please see

http://ruby-contract.rubyforge.org/
A9b6a93b860020caf9d2d1d58c32478f?d=identicon&s=25 Ross Bamford (Guest)
on 2006-05-28 13:18
(Received via mailing list)
On Sun, 2006-05-28 at 19:58 +0900, Daniel Schierbeck wrote:
> >
> > def ameth(foo, bar, baz, &blk)
> >   argsto($intclz, String, :sym)
> >   blk.call(foo, bar, baz)
> > end
>
> I don't think that'll work with "nested" constants, i.e. A::B::C. I
> think it may complicate things too much to allow classes/class names --
> besides, it should be the arguments, not the classes, that should handle
> the conversion.

Actually, the classes aren't handling the conversion - it's just calling
the standard coercion methods (String(), Integer(), etc). So you'd
probably have to define a BadgerMilk() method. Failing that, you just
pass lowercase names to get the (less strict) to_whatever conversion,
handled as you say by the arguments themselves, i.e. you could also do a
to_badgermilk where it made sense, and call argsto(:badgermilk) ).

Anyway, I did say it was a naive implementation ... :)
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 13:28
(Received via mailing list)
Okay, I've implemented it slightly different:

   require 'facet/binding'

   def args_to(types = {})
     Binding.of_caller do |binding|
       types.to_hash.each do |arg, type|
         binding[arg] = binding[arg].send("to_#{type}")
       end
     end
   end

   def test(foo, bar, baz)
     args_to :foo => :sym, :baz => :s
     return foo, bar, baz
   end

   p test("foo", "bar", 42)  #=> [:foo, "bar", "42"]


Daniel
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 13:28
(Received via mailing list)
Ross Bamford wrote:
>>>
> the standard coercion methods (String(), Integer(), etc). So you'd
> probably have to define a BadgerMilk() method. Failing that, you just
> pass lowercase names to get the (less strict) to_whatever conversion,
> handled as you say by the arguments themselves, i.e. you could also do a
> to_badgermilk where it made sense, and call argsto(:badgermilk) ).
>
> Anyway, I did say it was a naive implementation ... :)

I know; but it's always fun to deconstruct an implementation :)
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 13:31
(Received via mailing list)
Florian Groß wrote:
> I have implemented this in ruby-contract. Please see
>
> http://ruby-contract.rubyforge.org/

Nice, although a bit verbose as a general-usage implementation.


Daniel
E0ed615bd6632dd23165e045e3c1df09?d=identicon&s=25 Florian GroÃ? (Guest)
on 2006-05-28 13:55
(Received via mailing list)
Daniel Schierbeck wrote:

> Florian Groß wrote:
>> I have implemented this in ruby-contract. Please see
>>
>> http://ruby-contract.rubyforge.org/
>
> Nice, although a bit verbose as a general-usage implementation.

Hm, hard to make it any shorter without changing Ruby itself.

Here's your original:

>   def foo(str bar, i baz, sym bur)
>     # something
>   end

And here's how to do it with ruby-contract:

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

Perhaps it is time for writing another source code filter. What do you
think about?

>   def foo(<String> bar, <Integer> baz, <Symbol> bur)

That way you could use any expression where the type appears. Method
calls, variables and so on.
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 14:11
(Received via mailing list)
Florian Groß wrote:
> Daniel Schierbeck wrote:
> Perhaps it is time for writing another source code filter. What do you
> think about?
>
>>   def foo(<String> bar, <Integer> baz, <Symbol> bur)
>
> That way you could use any expression where the type appears. Method
> calls, variables and so on.

Hmmm, I'm not too fond of the use of classes -- I'd like to use the
#to_* methods instead. Otherwise the dynamic typing will suffer.

Perhaps this?

   def foo(<str> bar, <int> baz, <sym> bur); end

Although I still think this is more terse:

   def foo(str bar, int baz, sym bur); end


Daniel
E0ed615bd6632dd23165e045e3c1df09?d=identicon&s=25 Florian GroÃ? (Guest)
on 2006-05-28 14:29
(Received via mailing list)
Daniel Schierbeck wrote:

> Hmmm, I'm not too fond of the use of classes -- I'd like to use the
> #to_* methods instead. Otherwise the dynamic typing will suffer.

Nope, not at all. ruby-contract uses the same adaption routes that Ruby
uses:

>   # 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

So if it expects a Symbol anywhere it will accept a Symbol or any object
that has to_sym() and so on. In theory you could also add non-standard
adaption routes (like String => StringIO), but when you add more and
more of them you also add more and more sources of hard to detect bugs
to your application.

> Perhaps this?
>
>   def foo(<str> bar, <int> baz, <sym> bur); end
>
> Although I still think this is more terse:
>
>   def foo(str bar, int baz, sym bur); end

The downside of that is that you can't anymore use any expression. With
eh <> syntax you can also do this:

def foo(<type_for(:foo, 1)> bar)
def foo(<foo bar> qux)

At least the last one can't be done without an explicit delimiter. It
might be rare and exotic, but IMHO one of the great things of Ruby's
syntax is that you can use arbitrary expressions all over the place.
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 15:32
(Received via mailing list)
Florian Groß wrote:
> that has to_sym() and so on. In theory you could also add non-standard
> adaption routes (like String => StringIO), but when you add more and
> more of them you also add more and more sources of hard to detect bugs
> to your application.

That's exactly what I think is the problem; you have to tell Ruby how to
convert an object to another type anyway, so why not just use the
established convention? Besides, I don't think it should be mandatory
for all #to_* methods to return an * object -- I think it would be
completely okay if #to_a returned an object that responded to all
messages that an Array object would, in the same manner (e.g. #each
yielded a single object.)


>>   def foo(str bar, int baz, sym bur); end
> The downside of that is that you can't anymore use any expression. With
> eh <> syntax you can also do this:
>
> def foo(<type_for(:foo, 1)> bar)
> def foo(<foo bar> qux)

That's true -- maybe allow both? When the delimiters are used, the
content within is evaluated, and the returned symbol/class will be used.
If we decide to use symbols (given that this actually gets
implemented...) the return value should just respond to #to_sym.

   def foo(str bar, <"i" + "nt"> baz); end


Daniel
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2006-05-28 16:12
(Received via mailing list)
Hi --

On Sun, 28 May 2006, Daniel Schierbeck wrote:

> Now, I'm sure you'll all hate this, but I just had to post it.
>
> What if optional "type hinting", or, more accurately, "type conversion", was
> added to the method definition syntax?

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.

> I know it would save me a lot of typing -- plus, it's still dynamic; it
> wouldn't (necessarily) require the return values of the #to_* calls to be
> instances of a certain class.
>
> I know that some of you prefer to just call methods on the received
> arguments, and not use the #to_* methods first to convert them to a certain
> "type", but this proposal/idea wouldn't in any way make that more difficult
> than it is today.

It might make it less common, though -- that is, discourage duck
typing and reinforce the concept that what really matters is what
class an object is.


David
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-28 16:33
(Received via mailing list)
dblack@wobblini.net 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
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-28 18:43
(Received via mailing list)
>>   # 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.
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-28 18:55
(Received via mailing list)
>   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.
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-28 19:07
(Received via mailing list)
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.
E0ed615bd6632dd23165e045e3c1df09?d=identicon&s=25 Florian GroÃ? (Guest)
on 2006-05-28 20:09
(Received via mailing list)
transfire@gmail.com 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/Ker...

You can also see the unit tests at
http://ruby-contract.rubyforge.org/release/ruby-co...
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.
E0ed615bd6632dd23165e045e3c1df09?d=identicon&s=25 Florian GroÃ? (Guest)
on 2006-05-28 20:15
(Received via mailing list)
transfire@gmail.com 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.
E0ed615bd6632dd23165e045e3c1df09?d=identicon&s=25 Florian GroÃ? (Guest)
on 2006-05-28 20:15
(Received via mailing list)
transfire@gmail.com 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.
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2006-05-29 00:41
(Received via mailing list)
Hi --

On Sun, 28 May 2006, Daniel Schierbeck 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
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-29 07:20
(Received via mailing list)
dblack@wobblini.net 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
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2006-05-29 08:02
(Received via mailing list)
Hi --

On Mon, 29 May 2006, Daniel Schierbeck 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 :-)

> 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
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-29 10:26
(Received via mailing list)
Hi David,

dblack@wobblini.net 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 :P


> 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
This topic is locked and can not be replied to.