Feature #4264

General type coercion protocol for Ruby

Added by Charles Nutter over 3 years ago. Updated over 1 year ago.

[ruby-core:34354]
Status:Assigned
Priority:Normal
Assignee:Yukihiro Matsumoto
Category:-
Target version:next minor

Description

=begin
Justification: Ruby objects variously define toary, toint and others for either explicit (before call) or implicit (during call) coercion. However, the set of methods is limited, and adding more names just clutters namespaces and isn't feasible long-term anyway. This proposal will hopefully start a discussion toward adding a general type-coercion protocol via a #to or #toany method. To blunt the naming discussion a bit, I will refer to it as #tox.

Description: The #tox method will be a "supermethod" of sorts that can be used to coerce a given object to an arbitrary type. Where currently there are specific methods for coercing to specific types (toary, tostr), and other more general methods intended not for coercion but for explicitly re-structuring an object's data (toa, tos), there's no one protocol for doing general coercion. #tox would fill the roles of the coercion methods, accepting a target class and responding appropriately.

The response will depend on whether the target object can be coerced to the given type. The result for success should obviously be an instance of the target type. The result for failure could either be "soft": returning nil, or "hard": raising an error. There could also be an optional boolean flag that specifies hard or soft.

Existing coercion methods could (but need not be) implemented in terms of #to_x

def toary
to
x(Array)
end

def tostr
to
x(String)
end

Prior art: JRuby supports coercing Ruby objects to arbitrary Java types in this way. Currently only a set of hard-coded target types are supported for various core Ruby classes, but this is intended to eventually be part of the invocation protocol when calling Java. In other words, if the object being passed is not the exact type of the target parameter, JRuby will invoke tojava(targetparam_type) to do the coercion. Performance implications in this are obvious...so there may need to be discussions about modifying this protocol to make it easier to optimize.
=end

typecast.rb Magnifier (6.15 KB) Jonas Pfenniger, 01/12/2011 10:01 PM

noname (500 Bytes) Anonymous, 04/05/2012 03:53 AM

History

#1 Updated by Rodrigo Rosenfeld Rosas over 3 years ago

=begin

The result for failure could either be "soft": returning nil, or "hard": raising an error.

You mean #tox and #tox! ? By the way, I prefer obj.to(Array) instead of obj.to_any(Array)

Great idea! +1
=end

#2 Updated by Charles Nutter over 3 years ago

=begin
I like #to better as well, but isn't it too generic a name, sure to conflict with libraries out there?

Groovy uses #as

obj.as String

...though that's just as generic.
=end

#3 Updated by Rodrigo Rosenfeld Rosas over 3 years ago

=begin
Although I don't like Groovy very much (but still use it at a daily basis as part of my current job - at least it is not as bad as Grails - ARGH!), I think 'as' is also a good name and maybe even better than 'to'.
=end

#4 Updated by Konstantin Haase over 3 years ago

=begin
Both #as (Parslet, Sequel, MetaWhere, hacketyhack, Facets, Ruport, CouchPotato, ...) and #to (RSpec, Ramaze, ActiveSupport) are use in DSLs.
However, I really prefer #to.

Konstantin

On Jan 11, 2011, at 17:39 , Rodrigo Rosenfeld Rosas wrote:

Issue #4264 has been updated by Rodrigo Rosenfeld Rosas.

Although I don't like Groovy very much (but still use it at a daily basis as part of my current job - at least it is not as bad as Grails - ARGH!), I think 'as' is also a good name and maybe even better than 'to'.

http://redmine.ruby-lang.org/issues/show/4264


http://redmine.ruby-lang.org

=end

#5 Updated by Charles Nutter over 3 years ago

=begin
Other thoughts after talking with tenderlove:

  1. double-dispatched protocol

    The #to protocol could double-dispatch against the class, as in:

    def to(type)
    type.coerce(self)
    end

    This would allow defining coercions on the target class, rather than on the #to method. It does incur the hit of doing a double-dispatch though.

  2. symbolic types

    Allowing a symbol could make it possible to define coercions based on name alone, such as obj.to :yaml or obj.to :json. A default impl of coerce could be added to Symbol:

    class Symbol
    def coerce(obj)
    obj.send "to_#{self}"
    end
    end

    Given the double-dipatched protocol, the above may be left for users to implement, but it serves a similar purpose to the now-standard Symbol#to_proc.
    =end

#6 Updated by Rodrigo Rosenfeld Rosas over 3 years ago

=begin
Whatever what name is chosen, shouldn't we also define another method
like Object#from so that Object#to would be implemented as below?

def to(klass)
klass.from(self)
end

Rodrigo

Em 11-01-2011 14:46, Haase, Konstantin escreveu:

Both #as (Parslet, Sequel, MetaWhere, hacketyhack, Facets, Ruport, CouchPotato, ...) and #to (RSpec, Ramaze, ActiveSupport) are use in DSLs.
However, I really prefer #to.

Konstantin

On Jan 11, 2011, at 17:39 , Rodrigo Rosenfeld Rosas wrote:

=end

#7 Updated by Rodrigo Rosenfeld Rosas over 3 years ago

=begin
Sorry, I didn't see this message when I posted the last one.

Em 11-01-2011 15:09, Charles Nutter escreveu:

Issue #4264 has been updated by Charles Nutter.

Other thoughts after talking with tenderlove:

  1. double-dispatched protocol

The #to protocol could double-dispatch against the class, as in:

def to(type)
type.coerce(self)
end

This would allow defining coercions on the target class, rather than on the #to method. It does incur the hit of doing a double-dispatch though.

  1. symbolic types

Allowing a symbol could make it possible to define coercions based on name alone, such as obj.to :yaml or obj.to :json. A default impl of coerce could be added to Symbol:

class Symbol
def coerce(obj)
obj.send "to_#{self}"
end
end

Given the double-dipatched protocol, the above may be left for users to implement, but it serves a similar purpose to the now-standard Symbol#to_proc.

=end

#8 Updated by Charles Nutter over 3 years ago

=begin
On Tue, Jan 11, 2011 at 10:27 AM, Jim Weirich jim.weirich@gmail.com wrote:

(1) This is a extending the concept of toary and tostr to arbitrary
classes, (as opposed to extending the concepts of toa and tos).  Correct?

Correct.

(1a) Do we need a general solution for the toa/tos situation too?

Maybe? The big problem with toary/tostr versus toa/tos is that
there's no clear understanding of the differences. Up through 1.8.7,
toa was defined on all objects, with the default behavior just
returning a one-element array of that object. to
s is still defined on
all objects. On the contrary, toary and tostr are specifically
implemented only on objects that have a "natural" conversion to those
types.

I guess I see the difference being that toary/tostr are like
slightly softer type-casting operations and toa/tos are hard,
unconditional conversions. What would the equivalent be for "to"?

(2) Will there be a way of specifying the conversion in both directions?
 For example, if I add my own container type (JimContainer), will I be able
to specify the coercions Array->JimContainer and JimContainer->Array?

I added to the bug a proposal for double-dispatch; I think that would
address it neatly:

class JimContainer
def to(type)
case type
when Array
(your array conversion logic)
...
end

and Array would simply dispatch to JimContainer.coerce(ary).

  • Charlie

=end

#9 Updated by Charles Nutter over 3 years ago

=begin
FWIW, JRuby's to_java does support real types and symbolic types, as in the following equivalent calls:

'foo'.tojava # defaults to java.lang.Object, so we produce a java.lang.String
'foo'.to
java :string # known "core" type name, so we know to do java.lang.String
'foo'.to_java java.lang.String # specific type
=end

#10 Updated by Charles Nutter over 3 years ago

=begin
Tom Enebo proposed (in an offhand way) an additional #to? that simply returns whether the coercion would succeed. So the following code:

obj.tostr if obj.respondto? :to_str

Would become

obj.to(String) if obj.to?(String)

Some backstory on why I like this idea: In JRuby we often have to choose from multiple method overloads with different types of parameters. Having a to? protocol would be necessary for us to allow users to define their own java coercions, since without to? we won't know which types they support (without potentially forcing them to coerce, an expensive operation before we've decided that we have found the right overload to invoke).
=end

#11 Updated by Charles Nutter over 3 years ago

=begin
A question was raised about whether #to should understand context, as in knowing a top-level object being coerced to YAML should include the --- header. The answer seems to come from Marshal.

Marshal.dump(object) knows how to produce the marshal header, linking, and so on. Marshal is the master of this format. But Marshal defers to the objects themselves for actual content to go into that marshaled output, calling dump or marshaldump on each object in turn. With the #to protocol, Marshal.dump could be implemented as:

def Marshal.dump(obj)
emit_header
obj.to(Marshal)
end

In the same way that marshal_dump is used today.

Another concern raised is whether #to should be expected on all objects all the time, rather than just always calling MyClass.coerce(obj). The answer is simple: you want individual source types to control how they coerce to a target type, rather than expecting the target type to know about all possible sources. The default protocol where #to calls type.coerce would simply be a default behavior.
=end

#12 Updated by Charles Nutter over 3 years ago

=begin
On Tue, Jan 11, 2011 at 12:23 PM, Jim Weirich jim.weirich@gmail.com wrote:

On Jan 11, 2011, at 1:17 PM, James Edward Gray II wrote:

I understood it as toa/tos are for conversions.  They basically mean
change this object into an Array or a Sring, however it makes sense to do
that for the current object.

In contrast, toary/tostr are for objects that essentially are enhanced
versions of Array or String.  They mean, it's OK to pretend this object is
an Array or a String.

Perhaps I'm wrong though.

That's my understanding as well.

The logic is inconsistently applied in Ruby right now. Sometimes
tostr means treat me as a string without any additional calls,
sometimes it's called blindly, sometimes it's checked and called...and
sometimes to
s is used instead. However tostr and toary are used
much more heavily during call protocols (usually by the receiver) than
toa and tos. The latter two are more heavily used for user-driven
conversions.

Whether what's described above is correct or not (or desirable), I
think the current real-world uses of toary/tostr fit my description
better.

The lack of a formal statement on coercion/conversion protocols is
probably responsible for this confusion in the first place.

  • Charlie

=end

#13 Updated by Kurt Stephens over 3 years ago

=begin
Below is the simple, extensible technique used in Oaklisp.
Give each Module a coercer Symbol.
Apply the target type's coercer to the object to be coercer.
Cost is two message sends.

class Module
IDENTITYPROC = lambda { | obj | obj }
def coercer
unless @coercer
@coercer = :"#{self.name}
coercer"
definemethod(@coercer, &IDENTITYPROC)
end
@coercer
end
end

class Object
def as(mod)
send(mod.coercer, self)
end
end

#########################################

class String
definemethod(Float.coercer) { | obj | obj.tof }
end

class Float
definemethod(String.coercer) { | obj | obj.tos }
end

#########################################
require 'pp'

x = 1.23
pp [ x, String.coercer, x.as(String) ]
pp [ x, Float.coercer, x.as(Float) ]

x = "2.34"
pp [ x, String.coercer, x.as(String) ]
pp [ x, Float.coercer, x.as(Float) ]

##########################

[1.23, :Stringcoercer, "1.23"]
[1.23, :Floatcoercer, 1.23]
["2.34", :String
coercer, "2.34"]
["2.34", :Float
coercer, 2.34]

=end

#14 Updated by Shota Fukumori over 3 years ago

=begin
On Wed, Jan 12, 2011 at 2:10 AM, Rodrigo Rosenfeld Rosas
rr.rosas@gmail.com wrote:

 Whatever what name is chosen, shouldn't we also define another method like
Object#from so that Object#to would be implemented as below?

def to(klass)
   klass.from(self)
end

Rodrigo

Em 11-01-2011 14:46, Haase, Konstantin escreveu:

Both #as (Parslet, Sequel, MetaWhere, hacketyhack, Facets, Ruport,
CouchPotato, ...) and #to (RSpec, Ramaze, ActiveSupport) are use in DSLs.
However, I really prefer #to.

Konstantin

On Jan 11, 2011, at 17:39 , Rodrigo Rosenfeld Rosas wrote:

+1. Good Idea.

  • Object#to(klass, *args)
  • Class.from(obj, *args)
    ##

    "10".to(Integer) #=> 10
    Integer.from("10") #=> 10

    10.to(String, 8) #=> "12"
    String.from(10, 8) #=> "12"

    ##

    Shota Fukumori a.k.a. @sora_h - http://codnote.net/

=end

#15 Updated by Yukihiro Matsumoto over 3 years ago

=begin
Hi,

In message "Re: Re: [Ruby 1.9-Feature#4264][Open] General type coercion protocol for Ruby"
on Wed, 12 Jan 2011 02:14:24 +0900, Charles Oliver Nutter headius@headius.com writes:

|> (1a) Do we need a general solution for the toa/tos situation too?
|
|Maybe? The big problem with toary/tostr versus toa/tos is that
|there's no clear understanding of the differences.

I think I have explained before but I couldn't find the reference, I
will restate here.

  • tos, toi, tof, toa etc. are conversion method. For example, if
    a object can be converted into a string, it would have to_s method,
    but it doesn't have to behave like a string.

  • tostr, toint, toary are implicit conversion method. Some
    objects mimic built-in objects do not have same internal
    structures, but C defined methods requires the structure, so to
    str
    and the likes work as hooks to retrieve C structure like RString.
    The object that provide to_str should behave as a string.

  • String() and Array() are conversion methods, so (in case of String)
    it first try tostr to check if they are string, then try tos to
    get string object.

    If there's any usage of to_x methods that does not fit above
    principles, I consider it as a bug.

    I think we are discussing general conversion way to replace tos,
    etc. here, not to
    str and alike.

                        matz.
    

=end

#16 Updated by Charles Nutter over 3 years ago

=begin
On Tue, Jan 11, 2011 at 7:18 PM, Yukihiro Matsumoto matz@ruby-lang.org wrote:

 * tos, toi, tof, toa etc. are conversion method.  For example, if
  a object can be converted into a string, it would have to_s method,
  but it doesn't have to behave like a string.

 * tostr, toint, toary are implicit conversion method.  Some
  objects mimic built-in objects do not have same internal
  structures, but C defined methods requires the structure, so to
str
  and the likes work as hooks to retrieve C structure like RString.
  The object that provide to_str should behave as a string.

I guess I'm confused what the difference is. For example, sometimes
to_ary is used as a marker, and not invoked. Sometimes it is invoked
and expected to return an array. There are many cases in RubySpec
showing both behaviors. So it seems to be a more narrow conversion
method, but it still seems like a conversion method to me.

 * String() and Array() are conversion methods, so (in case of String)
  it first try tostr to check if they are string, then try tos to
  get string object.

If there's any usage of to_x methods that does not fit above
principles, I consider it as a bug.

I think we are discussing general conversion way to replace tos,
etc. here, not to
str and alike.

Based on your above description, I agree.

Can you provide more clarification:

  • Are toary/tostr/to_int intended only for use from core methods?
  • Are they expected to always return the an instance of the appropriate type?
  • Should users be defining these methods to produce new structures in
    the same way as they define to_a, or should they only be doing so if
    their class can duck-type as an array/string/integer for all
    array/string/integer methods?

  • Charlie

=end

#17 Updated by Michael Fellinger over 3 years ago

=begin
On Wed, Jan 12, 2011 at 1:46 AM, Haase, Konstantin
Konstantin.Haase@student.hpi.uni-potsdam.de wrote:

Both #as (Parslet, Sequel, MetaWhere, hacketyhack, Facets, Ruport, CouchPotato, ...) and #to (RSpec, Ramaze, ActiveSupport) are use in DSLs.
However, I really prefer #to.

Just wanted to point out that the #to method is only defined on two
specific instances in Ramaze, so it will not cause any conflicts to
worry about.

Konstantin

On Jan 11, 2011, at 17:39 , Rodrigo Rosenfeld Rosas wrote:

Issue #4264 has been updated by Rodrigo Rosenfeld Rosas.

Although I don't like Groovy very much (but still use it at a daily basis as part of my current job - at least it is not as bad as Grails - ARGH!), I think 'as' is also a good name and maybe even better than 'to'.

http://redmine.ruby-lang.org/issues/show/4264


http://redmine.ruby-lang.org

--
Michael Fellinger
CTO, The Rubyists, LLC

=end

#18 Updated by Jonas Pfenniger over 3 years ago

=begin
I have this version from 2004 that is lying around that does approximately the same. It was included in the factets gem but got pulled out at some time. It uses #castto and #castfrom as the methods of coercion. Each conversion is defined as a method either on the instance of the object that calls #castto (#castto#{targetclass}), or on the target class as #castfrom#{origin_klass}. I'm not totally happy about it, since it has a strong dependence on the class name, which means that inheriting classes cannot inherit the conversion mechanisms.
=end

#19 Updated by Yukihiro Matsumoto over 3 years ago

=begin
Hi,

In message "Re: Re: [Ruby 1.9-Feature#4264][Open] General type coercion protocol for Ruby"
on Wed, 12 Jan 2011 14:09:47 +0900, Charles Oliver Nutter headius@headius.com writes:

|I guess I'm confused what the difference is. For example, sometimes
|to_ary is used as a marker, and not invoked. Sometimes it is invoked
|and expected to return an array. There are many cases in RubySpec
|showing both behaviors. So it seems to be a more narrow conversion
|method, but it still seems like a conversion method to me.

Now the world of Ruby is huge, so I could not be responsible for all,
but the original intention of to_ary and alike are implicit conversion
to retrieve C structure for C defined methods. Using it as a mere
marker is not a good idea, I think.

|Can you provide more clarification:
|
|* Are toary/tostr/to_int intended only for use from core methods?

Basically they are for C implemented methods. Ruby implemented method
should based on duck typing.

|* Are they expected to always return the an instance of the appropriate type?

They are expected to return an instance of appropriate type all the
time, no exception. If you cannot provide corresponding object, you
should not define to_ary for the class.

|* Should users be defining these methods to produce new structures in
|the same way as they define to_a, or should they only be doing so if
|their class can duck-type as an array/string/integer for all
|array/string/integer methods?

I strongly suggest that users should define to_ary only when the
object behaves just like an array.

Did I make myself clear?

                        matz.

=end

#20 Updated by Charles Nutter over 3 years ago

=begin
On Wed, Jan 12, 2011 at 10:11 AM, Yukihiro Matsumoto matz@ruby-lang.org wrote:

Did I make myself clear?

Perfectly, thank you. This should be preserved for all time :) Bottom
line, don't define those methods unless you are 100% compatible with
the type they reference, and generally they're only intended for use
from core methods that need to get those types of objects.

So the proposal is intended for userland conversion methods like toa,
to
s, to_yaml, and friends.

  • Charlie

=end

#21 Updated by Charles Nutter over 3 years ago

=begin
Jonas: Cool! This is basically the exact protocol we're talking about here, just with different names. It actually also does Jim's suggestion, having the class-side to_ methods include the class name, as in your example of to_string. I like it.
=end

#22 Updated by Kurt Stephens over 3 years ago

=begin
The:

def to(targetmodule, *args)
send("to
#{target_module.name}", *args)
end

protocol requires the dynamic construction of a Symbol (and/or String), whereas the:

def to(targetmodule, *args)
send(target
module.coercer, *args)
end

protocol does not.

They both behave the same way, but the latter is easier to implement efficiently.

Some Rubies could inline targetmodule.coercer and perhaps even inline a specialization of #to(targetmodule).

Module#coercer can return any Symbol (too bad Ruby doesn't have anonymous Symbols).

Some syntactic sugar/module macros could abstract the coercing method away from the protocol:

class MyClass
definetomethod(String) do
"yo!"
end
end
MyClass.new.to(String)

=end

#23 Updated by mathew murphy over 3 years ago

=begin
On Tue, Jan 11, 2011 at 10:26, Charles Nutter redmine@ruby-lang.org wrote:

Issue #4264 has been updated by Charles Nutter.

I like #to better as well, but isn't it too generic a name, sure to conflict with libraries
out there?

Two-letter method names make me uneasy. How about obj.cast(Class)?

Or castto / castfrom, as in Jonas Pfenniger's patch.

mathew
[ Not a big fan of attr_* either ]

=end

#24 Updated by Rodrigo Rosenfeld Rosas over 3 years ago

=begin
On 19-01-2011 08:05, mathew wrote:

On Tue, Jan 11, 2011 at 10:26, Charles Nutterredmine@ruby-lang.org wrote:

Issue #4264 has been updated by Charles Nutter.

I like #to better as well, but isn't it too generic a name, sure to conflict with libraries
out there?
Two-letter method names make me uneasy. How about obj.cast(Class)?

Or castto / castfrom, as in Jonas Pfenniger's patch.

mathew
[ Not a big fan of attr_* either ]

I like the castto/castfrom names and I guess it avoids conflict with
most libraries.

Rodrigo.

=end

#25 Updated by Charles Nutter over 3 years ago

=begin
On Wed, Jan 19, 2011 at 5:54 AM, Rodrigo Rosenfeld Rosas
rr.rosas@gmail.com wrote:

I like the castto/castfrom names and I guess it avoids conflict with most
libraries.

They're not bad, but casting to me means something altogether
different: treating a an object as a different type, while still
referencing the same object.

I don't know what "cast" means generally in language design/type
theory, but it seems wrong for what's happening here. In our case,
we're asking the object to convert itself to a specific type...not
casting it to a type it already implements.

Perhaps convertto and convertfrom would be more in line with what's happening?

  • Charlie

=end

#26 Updated by Rodrigo Rosenfeld Rosas over 3 years ago

=begin
Em 19-01-2011 19:53, Charles Oliver Nutter escreveu:

On Wed, Jan 19, 2011 at 5:54 AM, Rodrigo Rosenfeld Rosas
rr.rosas@gmail.com wrote:

I like the castto/castfrom names and I guess it avoids conflict with most
libraries.
They're not bad, but casting to me means something altogether
different: treating a an object as a different type, while still
referencing the same object.

I don't know what "cast" means generally in language design/type
theory, but it seems wrong for what's happening here. In our case,
we're asking the object to convert itself to a specific type...not
casting it to a type it already implements.

Perhaps convertto and convertfrom would be more in line with what's happening?

  • Charlie

convert_to/from is ok to me too. Does someone else object this name?

Rodrigo

=end

#27 Updated by mathew murphy about 3 years ago

=begin
On Wed, Jan 19, 2011 at 15:53, Charles Oliver Nutter
headius@headius.com wrote:

They're not bad, but casting to me means something altogether
different: treating a an object as a different type, while still
referencing the same object.

Casting refers to treating a value as a value of a different type,
that's true; however, it may or may not reference the same storage
(object, memory or register) after casting.

Consider the C code

int i = 4;
float j = (float) i;

which depending on the machine architecture and compiler, could result
in the second variable referring to a different register, perhaps even
on a different processor.

Having said that, I prefer "convertto" and "convertfrom" because
"cast" in this sense is specialist jargon, and why use jargon when
it's not necessary?

mathew

=end

#28 Updated by Yusuke Endoh about 2 years ago

  • Description updated (diff)
  • Status changed from Open to Assigned
  • Assignee set to Yukihiro Matsumoto

#29 Updated by Yusuke Endoh about 2 years ago

  • Assignee changed from Yukihiro Matsumoto to Yusuke Endoh

#30 Updated by Yusuke Endoh about 2 years ago

  • Assignee changed from Yusuke Endoh to Yukihiro Matsumoto

Hello,

I can't remember why I assigned this to myself...
I think I can do nothing about this ticket.
So I assign this to matz.

Sorry I did not follow the discussion, but I couldn't understand this issue at all.
I guess it is helpful to restate:

  • what is the problem you think?
  • what are you proposing?
  • how does your proposal solve the problem?

by using sample code?

Yusuke Endoh mame@tsg.ne.jp

#31 Updated by Charles Nutter about 2 years ago

Mostly I'd like to see some consistency in coercion supported by a base protocol in Ruby itself.

The rough proposal was that BasicObject could have something like #convert_to(cls) that attempts to call a conversion method on the target class, passing self.

class BasicObject
def convert_to(cls)
cls.convert(self)
end
end

This double-dispatching protocol could allow simple conversions to be implemented in terms of Class#convert:

class BasicObject
def toa
convert
to(Array)
end
end

class Array
def self.convert(obj)
[obj]
end
end

And so on. But the more valuable benefit of the protocol would be that instead of having everyone add methods to BasicObject for their own conversion type, they'd add convert impls to their custom classes

class MyDataType
def self.convert(obj)
# logic for converting arbitrary objects to MyDataType
end
end

There's many dimensions to this discussion, of course, but I wanted to start the discussion about making Ruby's coercion protocols a bit more formal and supported by standard APIs.

#32 Updated by Thomas Sawyer about 2 years ago

This may be of some interest to the conversation:

http://rubyworks.github.com/platypus/

It includes a derivation of typecast system originally developer by Jonas Pfenniger. Interestingly I came across this too:

http://bugs.ruby-lang.org/attachments/1412/typecast.rb

#33 Updated by Thomas Sawyer about 2 years ago

Oh, I just noticed that attachment is actually from earlier in the conversion.

#34 Updated by Yusuke Endoh about 2 years ago

Hello,

Thank you, but still I'm not sure...
If we are writing the following code:

class Foo
def toa
...converting Foo to Array...
end
end
class Bar
def to
a
...converting Bar to Array...
end
end
class Baz
def to_a
...converting Baz to Array...
end
end

we will rewrite the following in your proposal, right?

class Array
def convert(obj)
case obj
when Foo
...converting Foo to Array...
when Bar
...converting Bar to Array...
when Baz
...converting Baz to Array...
end
end
end

I can't understand why this is consistent.

--
Yusuke Endoh mame@tsg.ne.jp

#35 Updated by Thomas Sawyer about 2 years ago

I really don't see how this can be about the "light" conversions, toa, toi, tos, etc. It only makes sense to me as being for the strict conversions toary, toint, tostr. Why? Because many different methods can result in array, integer, or string, etc. These common method names are just terms used to produce a common or typical form.

Also the use of case statement in the double dispatch is not a good idea. We need to be able to define these per class. Maybe something like:

class Array
define_conversion(Foo) do |obj|
...converting Foo to Array...
end

define_conversion(Bar) do |obj|
  ...converting Bar to Array...
end

define_conversion(Baz) do |obj|
   ...converting Baz to Array...
end

end

#36 Updated by Charles Nutter about 2 years ago

Yusuke: Individual classes can (and often should) still define their own to_a, etc. There's no reason to move the current logic into a slower case/when statement.

The convertto(cls) method would just be a standard way to allow new libraries to handle arbitrary object types without monkey-patching. For example, rather than monkey-patching a toxml into Object or BasicObject, an XML library could just define XML.convert(obj) that contains that logic. Users would then just call obj.convert_to(XML).

#37 Updated by Anonymous about 2 years ago

On Thu, Apr 05, 2012 at 12:28:38AM +0900, headius (Charles Nutter) wrote:

Issue #4264 has been updated by headius (Charles Nutter).

Yusuke: Individual classes can (and often should) still define their own to_a, etc. There's no reason to move the current logic into a slower case/when statement.

The convertto(cls) method would just be a standard way to allow new libraries to handle arbitrary object types without monkey-patching. For example, rather than monkey-patching a toxml into Object or BasicObject, an XML library could just define XML.convert(obj) that contains that logic. Users would then just call obj.convert_to(XML).

So implementation would just be something like this:

class Object
def convert_to obj
obj.convert self
end
end

It seems very nice. Hopefully it would discourage people from monkey
patching things like to_json on to object. This type of monkey
patching is a real world problem we (the Rails team) has to deal with.
Several JSON libraries would implement to_json, and when people had
them both activated at the same time, things would explode.

Fortunately we've worked around those issues.

I think this will help mitigate the "multi-monkey patch" problem, but as
long as people can monkey patch, those types of bugs will happen. :(

--
Aaron Patterson
http://tenderlovemaking.com/

#38 Updated by Yusuke Endoh about 2 years ago

=begin
Hello, headius

Thank you. Is your problem about just monkey-patching?
Please check the following summary.

== Problem
Many monkey-patching like below is used in some libraries, such as xml and json:

class Integer
def tojson
...
end
end
class Array
def to
json
...
end
end
...

The motivation of this proposal is to remove these monkey-patching.

== Proposal
You want Ruby to provide the following simple one method:

class Object
def convert_to(kls)
kls.convert(self)
end
end

The method name is still arguable, though.

== How the problem is solved
The libraries only have to define the following one method, instead of a lot of monkey-patching.

class JSON
def convert(obj)
case obj
when Integer
...
when Array
...
end
end
end

Note that a user should write convertto(JSON) instead of tojson.
=end

#39 Updated by Yusuke Endoh about 2 years ago

And I'd like to add some question.

== Why do you want to remove the monkey-patching?

If you are worried about method name conflict, this proposal does not
solve the problem because the class names still collide.

If you dislike monkey-patching just because it is monkey-patching,
refinement (#4085) may be more general solution.

== Method name

I understand you want to avoid bikeshed, but I think it is inevitable
in this proposal.

== Performance

I don't think this is so big problem :-)

Yusuke Endoh mame@tsg.ne.jp

#40 Updated by Yusuke Endoh over 1 year ago

  • Target version set to next minor

Also available in: Atom PDF