to_s and to_i follow a naming convention that is not extensible (third party users write to_class, while the standard library methods write to_c), lack error checking, and pollute the method namespaces of classes that provide these conversions.String() and Integer(), which don't match the usual convention, and require the use of a user-defined dispatch mechanism to dispatch based on the source type.
Object (possible names: #to, #to_type, #as -- we'll use #to in the RCR) accepting a parameter wich represents the target type of the transformation.#to method, say:
a=some_string.to(:CapitalizedString)
a=3.to :Odd #=> 3
a=4.to :Odd #=> TypeError
is_a?, kind_of? andrespond_to? may be factored in a
def foo a,b
a=a.to Bla
b=b.to Boo
..do stuff
end
def sum a,b
a=a.as Numeric; b=b.as Numeric
a+b
end
sum 1,2
false, nil,NilClass, allowing users to write their own boolean#to_bool) without changing existing
foo= bla.to(Integer) rescue 0
Integer conversions) can be passed to the (#to,#as, #to_type) method, and
'200'.to Integer, 16
Integer considering it encoded inSortedCollection class. Converting anEnumerable object would be easy:
ConvRegistry[Enumerable,SortedCollection]= proc do |enum|
sca=SortedCollection.new
enum.each {|i| sc.insert i }
sc
end
Enumerable to a pseudoclass :Bool can be as easy as
ConvRegistry[Enumerable,:Bool]=proc { |x| not x.empty? }
ConvRegistry[Enumerable,:Bool]=proc { ... }
ConvRegistry[String,Integer]= proc { ... }
ConvRegistry[Class,Enumerable]= proc ... }
rb_define_conversion(rb_cEnumerable, rb_intern("Bool"),enum_to_bool);
rb_define_conversion(rb_cString, rb_cInteger, string_to_int);
rb_define_conversion(rb_cClass, rb_cEnumerable, class_to_enum);
class ConvRegistry
@@reg=Hash.new
def self.[] from_type,to_type
if res=@@reg[[from_type,to_type]]
res
elsif res= find_in_ancestors(from_type,to_type)
res
else
raise TypeError.new("no conversion for #{from_type},#{to_type}")
end
end
def self.find_in_ancestors from_type,to_type
from_type.ancestors[1..-1].each do |anc|
if res2= @@reg[[anc,to_type]]
return res2
end
end
nil
end
def self.[]= from_type,to_type,func
@@reg[[from_type,to_type]]=func
end
end
class Object
def to(something,*args,&blk)
if something.is_a? Module and self.is_a? something
return self
end
ConvRegistry[self.class,something].call(self, *args,&blk)
end
end
if __FILE__== $0
require 'test/unit'
require 'stringio'
class MyTest < Test::Unit::TestCase
def setup
ConvRegistry[Enumerable,:Bool]=proc { |x| not x.empty? }
ConvRegistry[String,Integer]= proc { |x,*args| x.to_i *args }
ConvRegistry[Class,Enumerable]= proc { |x| x.send
:include,Enumerable }
ConvRegistry[Object,Enumerable]= proc {|x| x.send :extend,
Enumerable }
ConvRegistry[Object,:Frozen]= proc do |x|
if x.frozen?
x
else
raise TypeError.new
end
end
ConvRegistry[Integer,:Odd]= proc do |x|
if x%2==1
x
else
raise TypeError.new
end
end
ConvRegistry[Object,:Readable]= proc do |x|
if x.respond_to? :read
x
else
raise TypeError.new
end
end
end
def test_class_class
assert_equal '5'.to(Integer),5
end
def test_subclass_class
my=Class.new String
m=my.new '5'
assert_equal m.to(Integer),5
end
def test_class_module
f_class=Class.new(Object)
f_class.class_eval do
def each
yield 1
end
end
f_obj=f_class.to(Enumerable).new
assert_equal f_obj.find_all {|x| x==1}, [1]
end
def test_more_arguments
a='aa'
assert_equal 170,a.to(Integer, 16)
end
def test_instance_module
f_class=Class.new Object
f_class.class_eval do
def each
yield 1
end
end
f_obj=f_class.new
f_obj=f_obj.to Enumerable
assert_equal f_obj.find_all {|x| x==1}, [1]
end
def test_singleton_module
f=Object.new
def f.each
yield 1
end
f=f.to(Enumerable)
assert_equal f.find_all {|x| x==1}, [1]
end
def test_property_pseudoclass_ok
a= 'ciao'
a.freeze
a=a.to(:Frozen)
assert_equal a, 'ciao'
end
def test_property_pseudoclass_fail
assert_raises(TypeError) {'ciao'.to :Frozen}
end
def test_property_pseudoclass_ok2
a=5
a=a.to :Odd
assert_equal a,5
end
def test_property_pseudoclass_fail2
assert_raises(TypeError) {6.to :Odd}
end
def test_object_superclass
a=42
assert_equal a, a.to(Integer)
end
def test_object_mixin_ok
a=[]
assert_equal a,a.to(Enumerable)
end
def test_object_mixin_fail
a=5
assert_raises(TypeError) {a.to Enumerable}
end
def test_methodbag_pseudoclass_ok
a=StringIO.new
assert_equal a,a.to(:Readable)
end
def test_methodbag_pseudoclass_fail
a=24
assert_raises(TypeError) {a.to :Readable}
end
def test_enumerable_bool
a=''
assert_equal false, a.to( :Bool)
a << 1
assert_equal true , a.to( :Bool)
a=[]
assert_equal false, a.to( :Bool)
a << 1
assert_equal true , a.to( :Bool)
end
end
end
I don't think ruby needs a general type conversion mechanism, because you don't have static typing that requires many conversions.
I prefer methods to_x b/c they describe precisely which conversions are supported by the developer. You have to write those methods anyways. Put another way, I think Object#to is syntactic sugar that makes a class more confusing.
~ patrick may (sorry I didn't sign originally)
First, I don't think static typing needs much more conversions than ruby's type system. For the second part, is not just syntax sugar. The to_x thing is nice because it is short, but it leaves the need for to_set, to_enum, Hash[] and Integer(). Not everyond can write single character names :) and anyway you end up polluting every class with a conversion to everything else. The larger the code base, larger the name pollution.
Also note that none of to_f, to_set, to_enum, Hash[] and Integer() follow the same convention then the others.
Also you can't expect that DeveloperA can write everything for you, simply because some thing does not exist when he is writing. Say, Array can't support Set convertions, but set.rb does add Array#to_set. The proposed system just make this consistent and extensible. Hope this makes thing clearer. --gabriele renzi
T.
I like that the current convention encourages encourages standard methods (messages) over standard types.
~ patrick may
-- Paul Brannan
I don't think this proposal makes sense unless one wants to do lots of class conversions [1] . I don't think conversions to standard types qualifies as 'lots of conversions'. So I ask, why would one do lots of class conversions, especially conversions from one non-standard type to another?
The use case I come up with is class casting -- This method expects a Foo, so I need to convert this object to a Foo. I don't like that, I like this convention better: This method expects a parameter that responds to #foo and #bar.
Type conversion is a smell to me. Type conversions are about changing data. While necessary, IMHO data migrations (even in the small level of in-memory data) lead to hard and thorny problems [2]. The better design is to re-engineer and reduce the type conversions, instead of trying to make it easier to manage many type conversions.
Cheers,
Patrick
p.s. I don't see what's wrong with Hash[] or Integer(). Personally, I've never used them, b/c I use { } or #to_i. If we need method convention for hashes, I think #to_h or #to_hash is fine for me.
1. If the number of class conversions is small, then most invocations of Object#to will be errors. There will be a documentation problem in identifying the valid invocations of Object#to. If most conversions are to standard types, I think #to_s or #to_a is better.
2. http://epubs.siam.org/sam-bin/dbq/article/42585
Enumerable. A CGI->FCGI or CGI->WEBrick converter may allow you to use the same code with different backends. And notice that you need Integer() when you want do discriminate valid data, cause 'foo'.to_i gives 0.
Related to "the other side" of this RCR, let me say I don't think most of the conversions would be needed beetween standard types, but in external code.
My point is that you can't refactor the whole RAA/RubyForge, you won't be able to write code based on a mixin available in both rubymail/tmail (or REXML/libxml2 or Cerise::FormHandler/Form::Validator). You would need to write an adaption layer, and this RCR gives you a simple framework in which you can work. It is nothing strange, just a Grand High Exalted interface systems ;)
--gabriele renzi
If there is a standard method that two classes support, and all you want is to make sure you have something of that type, then there should be a way to short circuit the transformation early. That is, given "Foo < DelegateClass(String)", and
def bar( anyThing )
x = anyThing.to_s
..use x
end
there should be some way to abbreviate the fact that ConvRegistry[Foo, String] is just an identity transformation. The extra code in your implementation of Object#to works for class, but not type.
Also, #to isn't a very good, descriptive name for something that ends up being a bouncer method/assertion. Sure, it can do such a thing, but I'd rather it only attempt conversion. (x.to(:Even) would return (x/2).round * 2, while x.to(:Odd) would probably just add one if (x%10)<5, and subtract one if (x%10)>5, or something to that effect. However, it would still raise an exception if you fed it a string, or a regexp.)
--Zallus Kanite
About the name, I'm not sure I understand what you're saing: you first state that you don't like #to for a bouncer method, and I understand this. Actually I'm leaning more and more on the #as side in these days. But then you say it is ok. I'm dumb, Could you expand a little, please?
The only thing I get is that you want #as(Odd|Even) to change the object. :Odd and :Even are pseudotypes for which I found more reasonable to work just like assertions, but you're free to do what you want in you own code.
-- gabriele renzi
Back to RCRchive.
RCR Submission page and RCRchive powered by Ruby, Apache, RuWiki (modified), and RubLog