[RCR] Unified type conversion framework

Hi gurus and nubys,

You can find the full text at
http://rcrchive.net/rcr/RCR/RCR280
(sorry if there are typos/errors, I'm having some problems with the submit phase).

The proposed system unifies the various way to convert or to extract an object from another, like to_f, to_set, Integer() or to_enum.
It also allows declarative checking of the type of an object and reduces duplication of code. Yes, I mean type, not class ;).

But it's not something really new and does not break compatibility at all, it is mostly a refactoring and generalization of already used practices.

Anyway I think you could understand better reading the rationale and sample implementation on the rcr page. If you're going to vote against this please take some time to comment on why you did it, it would be much more useful.

Finally I'd like to thank Paul Brannan for helping me in refining this and David Black for the great help in the rcr publishing process :slight_smile:

Cool! Occasionally I think about doing something like this, so I can have some nice way to remember the custom conversions I use.
Maybe you could sneak some extra conversions in with it, like
[[1,2],[3,4]].as Hash # => {1=>2,3=>4}

Related to this is type-checking when you do want it (duck typing is good, but I'd give it up in some cases to have an easy, readable way of checking that I'm not passing a hash instead of an array).
I'd really like to be able to do:
def method(foo,bar)
  foo.is! Hash
  bar.can_be! Array
  #...
end
where they do something like
def is! t
  raise TypeError.new("Expected a #{t} but got a " +
            "#{self.type}: #{self.inspect}") unless is_a? t
end

def can_be! t
  raise TypeError.new("Can't convert to #{t} from "+
            "#{self.type}: #{self.inspect}") unless can_be? t
end

And can_be? is the does-this-conversion-exist method.
This might even be rdoc-able, if it was at the top of a method. It'd certainly make the ad-hoc documentation I write today executable.

Sam

Sam McCall ha scritto:

Cool! Occasionally I think about doing something like this, so I can have some nice way to remember the custom conversions I use.

I think most of us did, this is why I'm telling this is not something new :slight_smile:

Maybe you could sneak some extra conversions in with it, like
[[1,2],[3,4]].as Hash # => {1=>2,3=>4}

The system is open, I did thought of this when thinking how unintuitive Hash[anArray] is . This is another example of the need for a better approach, I guess there are much more :wink:

Related to this is type-checking when you do want it (duck typing is good, but I'd give it up in some cases to have an easy, readable way of checking that I'm not passing a hash instead of an array).
I'd really like to be able to do:
def method(foo,bar)
    foo.is! Hash
    bar.can_be! Array
    #...
end
where they do something like
def is! t
    raise TypeError.new("Expected a #{t} but got a " +
           "#{self.type}: #{self.inspect}") unless is_a? t
end

def can_be! t
    raise TypeError.new("Can't convert to #{t} from "+
           "#{self.type}: #{self.inspect}") unless can_be? t
end

And can_be? is the does-this-conversion-exist method.

#is! is broken, because it relies on is_a? that is not powerful enough to really express a type, but #as (or #to.. I mean, the proposed system) is powerful enough to expres any kind of type, including your #is! :slight_smile:

The same is said for can_be? Sure, I could introduce a method in the ConvTable that checks if a conversion path exists, but it won't work for, say,
  'ciao'.as Integer
because even if a String -> Integer conversion path do exist, you still have to check the whole string. So you end up needing #as again.

This might even be rdoc-able, if it was at the top of a method. It'd certainly make the ad-hoc documentation I write today executable.

exacylt what I was thinking of. The IdeaSpace is limited, it seem :wink:

I think you mean Hash[*anArray]. IMO, It's not totally unintuitive, but
it's not consistent with other conversions.

Paul

···

On Wed, Sep 08, 2004 at 06:40:06PM +0900, gabriele renzi wrote:

The system is open, I did thought of this when thinking how unintuitive
Hash[anArray] is . This is another example of the need for a better
approach, I guess there are much more :wink:

gabriele renzi wrote:

#is! is broken, because it relies on is_a? that is not powerful enough to really express a type, but #as (or #to.. I mean, the proposed system) is powerful enough to expres any kind of type, including your #is! :slight_smile:

I think there needs to be a difference between implicit conversions (if there are any) and explicit conversions though:
"hello".can_be! String # ok
"hello".is! String # ok (maybe this is just an alias to #to, but i like

                    # the clarity)
123.can_be! String # ok
123.is! String # raises
It's like the current difference between to_s and to_str, which seems a bit arbitrary. I'm not too unhappy with is_a here, a way to specify an interface rather than a class would be nice, but it doesn't seem to be a big problem for me anyway.

The same is said for can_be? Sure, I could introduce a method in the ConvTable that checks if a conversion path exists, but it won't work for, say,
'ciao'.as Integer
because even if a String -> Integer conversion path do exist, you still have to check the whole string. So you end up needing #as again.

Hmm, ok. Maybe
def can_be? t
   self.as t
   true
rescue TypeError
   false
end

This might even be rdoc-able, if it was at the top of a method. It'd certainly make the ad-hoc documentation I write today executable.

exacylt what I was thinking of. The IdeaSpace is limited, it seem :wink:

:slight_smile:
Type checking for parameters (along with identifier typos) is one of the few things that still irritates me, sometimes I think I'd be happier in a dynamic-except-for-function-parameters language :wink:
Sam

Paul Brannan wrote:

I think you mean Hash[*anArray]. IMO, It's not totally unintuitive, but
it's not consistent with other conversions.

Actually converting an assoc array to a Hash is done via Hash[*array.flatten]. I think that that is pretty low level. I'd prefer something like Hash.from_assoc()

Regards,
Florian Gross

Florian Gross wrote:

Paul Brannan wrote:

I think you mean Hash[*anArray]. IMO, It's not totally unintuitive, but
it's not consistent with other conversions.

Actually converting an assoc array to a Hash is done via Hash[*array.flatten]. I think that that is pretty low level. I'd prefer something like Hash.from_assoc()

At work we have Array#to_h in a library, it's really useful for things like:

db.select_all(...).map {|foo,bar| [foo,bar]}.to_h

Sam

Florian Gross wrote:

Paul Brannan wrote:

I think you mean Hash[*anArray]. IMO, It's not totally unintuitive,
but it's not consistent with other conversions.

Actually converting an assoc array to a Hash is done via
Hash[*array.flatten]. I think that that is pretty low level. I'd
prefer something like Hash.from_assoc()

At work we have Array#to_h in a library, it's really useful for things
like:

db.select_all(...).map {|foo,bar| [foo,bar]}.to_h

Sam

This is my approach (very similar) and I love it:

  require 'extensions/all'
  db.select_all(...).build_hash { |foo, bar| [foo,bar] }

Gavin

Well, as long as we're playing "show me yours and I'll show you mine," I
use (again, very similar):

module Enumerable
    def collect_hash
        result = {}
        each { |x| result = yield(x) }
        result
        end
    end

and thus would write (for the initial):

  db.select_all(...).collect_hash { |foo, bar| bar }

-- Markus

···

On Wed, 2004-09-08 at 21:17, Gavin Sinclair wrote:

> Florian Gross wrote:
>> Paul Brannan wrote:
>>
>>> I think you mean Hash[*anArray]. IMO, It's not totally unintuitive,
>>> but it's not consistent with other conversions.
>>
>>
>> Actually converting an assoc array to a Hash is done via
>> Hash[*array.flatten]. I think that that is pretty low level. I'd
>> prefer something like Hash.from_assoc()
>
> At work we have Array#to_h in a library, it's really useful for things
> like:
>
> db.select_all(...).map {|foo,bar| [foo,bar]}.to_h
>
> Sam

This is my approach (very similar) and I love it:

  require 'extensions/all'
  db.select_all(...).build_hash { |foo, bar| [foo,bar] }

Gavin

This would be nice too:

build_hash {|key, val| {key => val}}

martin

···

Gavin Sinclair <gsinclair@soyabean.com.au> wrote:

  require 'extensions/all'
  db.select_all(...).build_hash { |foo, bar| [foo,bar] }

Well, as long as we're playing "show me yours and I'll show you mine," I
use (again, very similar):

I like that game :slight_smile:

module Enumerable
    def collect_hash
        result = {}
        each { |x| result = yield(x) }
        result
        end
    end

and thus would write (for the initial):

  db.select_all(...).collect_hash { |foo, bar| bar }

-- Markus

You don't see a need for transforming the keys?

  db.select_all(...).build_hash { |foo, bar| [foo.to_i, bar.to_s] }

Gavin

Gavin Sinclair ha scritto:

Well, as long as we're playing "show me yours and I'll show you mine," I
use (again, very similar):

I like that game :slight_smile:

Let me play a little too:

    def build_hash (ary)
       result = {}
       ary.each do |elt|
         key, value = yield elt # maybe default behaviour is needed
         result[key] = value
       end
       result
     end

ConvRegistry[Array,Hash]= method :build_hash

[[1,2],[3,4]].as(Hash) {|k,v| [k,v] } #{1=>2, 3=>4}
[1,2,3,4].as(Hash) {|k| [k,true] } #{1=>true, 2=>true, 3=>true, 4=>true}

I call that "Enumerable#collect_hash_with_key"; in my extensions I tried
as much as possible to stay consistent with the built-ins, if for no
other reason than brain-tax.evasion.

-- MarkusQ

···

On Wed, 2004-09-08 at 21:36, Gavin Sinclair wrote:

You don't see a need for transforming the keys?