Crazy thought -- Rubyish type conversion?

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

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

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

Daniel Schierbeck wrote:

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 have implemented this in ruby-contract. Please see

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

···

--
http://flgr.0x42.net/

Hi --

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.

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.

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

···

On Sun, 28 May 2006, Daniel Schierbeck wrote:

--
David A. Black (dblack@wobblini.net)
* Ruby Power and Light, LLC (http://www.rubypowerandlight.com)
   > Ruby and Rails consultancy and training
* Author of "Ruby for Rails" from Manning Publications!
   > Ruby for Rails

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

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.

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

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

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.

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.

···

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

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

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

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.

···

--
http://flgr.0x42.net/

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.

···

--
http://flgr.0x42.net/

Hi --

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.

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

···

On Sun, 28 May 2006, Daniel Schierbeck wrote:

dblack@wobblini.net wrote:

--
David A. Black (dblack@wobblini.net)
* Ruby Power and Light, LLC (http://www.rubypowerandlight.com)
   > Ruby and Rails consultancy and training
* Author of "Ruby for Rails" from Manning Publications!
   > Ruby for Rails

Ross Bamford 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.

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

Daniel

···

On Sun, 2006-05-28 at 18:25 +0900, transfire@gmail.com wrote:

A::b::BadgerMilk :slight_smile:

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

  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.

dblack@wobblini.net wrote:

Hi --

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.

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

···

On Sun, 28 May 2006, Daniel Schierbeck wrote:

dblack@wobblini.net wrote:

Good point :slight_smile: 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]

···

On Sun, 2006-05-28 at 19:37 +0900, Daniel Schierbeck wrote:

Ross Bamford wrote:
> 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:
>
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 :slight_smile:

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk