As I said on ruby-talk, I think this is a bad idea. I don't think that this is something that can cleanly be generalised, and I
really don't think that to_* methods can be properly expressed in terms of from_* in any case. The needs and purposes of each may be subtly different. The more I use Ruby, the less that I think that a general-purpose conversion environment is valuable. Additionally, I believe that others have expressed more clearly *just* why this is a bad idea in the original thread regarding this concept. -Austin
As I said in this RCR, I'm only attempting to generalize converting from a known specific type to an arbitrary type. I don't think you can get much cleaner than the implementation I gave - just add some class from_* methods. Unlike other solutions, this one is very simple and coexists with the current instance to_* methods (which converts from an arbitrary type to a known specific type). I'm not proposing that anything change (or be deprecated) about the current to_* methods.
I'm not sure how often I want to convert strings to instances of classes. At least, I don't remember the occasion in my 10 years of Ruby programming.
- matz.
Matz, I assume you mean to instances of arbitrary classes, because I'm sure you've converted strings to Integers, Float, and Symbols. An example I gave on ruby-talk was a general command-line parser. The command-line starts as a bunch of strings. In your command-line spec, you may describe what arguments/options are what type (the spec would have the klass). Then you'd just call klass.from_s(s) to convert. Of course another solution to that problem would be to specify the conversion method (:to_i, :to_f, :to_s, etc), but it doesn't seem as clean. I'm sure others who have brought up having a general conversion mechanism have other examples. Since I've seen the conversion topic multiple times and the above seems like a very simple solution (that solved most cases), I thought I'd propose it.
I'm very surprised that the more complex RCR 280 has more support than this RCR.
Eric
I just don't get how str.to_i is cleaner than Integer.from_s(str), where we can use symbols to call methods. Besides that, I don't think generic from_s/to_s combination works for arbitrary classes. We have marshal (or something like YAML) for the purpose.
- matz.
Correct, klass.from_s doesn't work for arbitrary classes, because not all classes have a good way to convert a simple string to an instance of their class (in fact, most don't). This only applies to classes that have a good from_s method (or from_*).
BTW, I think this RCR is very similar to the Kernel methods #Integer, #Float, and #String. So if you have an arbitrary class (klass) that supports conversion from strings, you could do this:
send(klass.to_s.to_sym,str)
Assuming a method named the same as klass is in Kernel. This RCR has these methods more appropriately as class methods of klass instead of in Kernel.
--- Jim Freeze wrote:
> That's pretty interesting Eric, to grab the type off the
> default.
> I think I'll add that to CommandLine::OptionParser.
>
> However, I'm still not sure if I like the #from_s form,
> but I can see the utility of it. For the common cases,
> I can use a simple case statement:
>
> case default
> when Float then Float(arg)
> when Fixnum then Integer(arg)
> end
>
> But, as you can see with even these simple cases, there
> are big issues and big questions to answer.
> 1. Fixnum does not match Integer
No problem. Fixnum inherits from Integer. Do you care whether Fixnum.from_s returns a Fixnum or a Bignum? It could return either just like many of the other Fixnum instance methods.
> 2. Do we use to_i or Integer(#) - Integer raises and to_i does not
> 3. Do we use Float or to_f - Float raises and to_f does not
Good point. This RCR should to specify this. I would think it best if an exception occur if the full string doesn't parse the the target type. I'll change the implementation to use the methods that raise exceptions.
> Then, there are the tougher cases like
>
> require 'parsedate'
> case default
> when Time then Time.gm(*ParseDate.parsedate(arg))
I haven't dealt with dates and times to know what all the options are. I threw this in at the last minute. If you don't like the klass.from_s method, you could define your own derived class (or override that klass.from_s):
require 'parsedate'
class MyTime < Time
def self.from_s(s)
gm(*ParseDate.parsedate(s))
end
end
and then make the default be a MyTime instead of a Time.
> when Fixnum arg.to_i # what if yield bignum
answered above
> end
>
> where we have to use a help class and helper method (not new)
> to get
> the object we want. Or if the conversion method
> returns something other than what we requested, like
> Bignum instead of Fixnum.
>
> Sadly, the #from_s RCR doesn't seem to address any of these
> issues.
Eric
I don't think Integer.from_s(x) etc. are "methods more appropriately as class methods of klass instead of in Kernel". They are just not same. Besides that, sometimes functional form is more appealing than class method form.
- matz.
If you haven't noticed, I added an example that shows this RCR in action. I think this is the cleanest/most flexible solution with the given super-simple command line option spec: option => default (option value converted to default.class when given).
I see your example being neat. But I'm not sure it's enough to add from_s methods to the core to save a few lines of case statement.
In addition, I don't like the name "from_s".
- matz.
Fair enough. Maybe it'll go in some miscellaneous rubyforge project if it doesn't make it into std or built-in libs. I'm not at all tied to the name from_*. It just seemed to be a good complement to #to_*.
The concept of #from_s seems sound to me, from the standpoint of where methods belong. If you import an arbitrary class that can translate itself to/from a string, the class itself should have methods on it for that purpose, NOT muddy up the built-in string class to add them.
my_obj = MyClass.from_s( the_str ); my_obj.to_s
This should only exist in the case where a 1-1 correspondence exists between an instance of a class an its string representation. "foo bar" and "foo, bar" could both mean %w|foo bar|, so Array.from_s doesn't make sense, IMO.
Further, it makes sense to me that the set of core classes MAY cross the boundaries for simple interoperability. String#to_i makes sense, because String and Integer are both in the core.
In short - I think #from_s is a good pattern for your own custom classes, but doesn't need to be imposed on the core. It might be nice for consistency, but it wouldn't be consistent due to items like Array. - GavinKistner
I agree that Array.from_s doesn't make a lot of sense and I'm not proposing it.
Regarding naming, another option would be to put the word "new" in the name because it makes a new object of the class. Like perl's new_from_fd, it could be new_from_s. But, I still like simply from_s better.
A crazy idea (not putting it in the proposal) would be to make a default for this:
class Class
def from_s(s)
eval("new(#{s})")
end
end
but you'd want to somehow make sure s didn't have any variable references in it (including indirectly to self).
As I said in this RCR, I'm only attempting to generalize converting from a known specific type to an arbitrary type. I don't think you can get much cleaner than the implementation I gave - just add some class from_* methods. Unlike other solutions, this one is very simple and coexists with the current instance to_* methods (which converts from an arbitrary type to a known specific type). I'm not proposing that anything change (or be deprecated) about the current to_* methods.
I'm not sure how often I want to convert strings to instances of classes. At least, I don't remember the occasion in my 10 years of Ruby programming.
- matz.
Matz, I assume you mean to instances of arbitrary classes, because I'm sure you've converted strings to Integers, Float, and Symbols. An example I gave on ruby-talk was a general command-line parser. The command-line starts as a bunch of strings. In your command-line spec, you may describe what arguments/options are what type (the spec would have the klass). Then you'd just call klass.from_s(s) to convert. Of course another solution to that problem would be to specify the conversion method (:to_i, :to_f, :to_s, etc), but it doesn't seem as clean. I'm sure others who have brought up having a general conversion mechanism have other examples. Since I've seen the conversion topic multiple times and the above seems like a very simple solution (that solved most cases), I thought I'd propose it.
I'm very surprised that the more complex RCR 280 has more support than this RCR.
Eric
I just don't get how str.to_i is cleaner than Integer.from_s(str), where we can use symbols to call methods. Besides that, I don't think generic from_s/to_s combination works for arbitrary classes. We have marshal (or something like YAML) for the purpose.
- matz.
Correct, klass.from_s doesn't work for arbitrary classes, because not all classes have a good way to convert a simple string to an instance of their class (in fact, most don't). This only applies to classes that have a good from_s method (or from_*).
BTW, I think this RCR is very similar to the Kernel methods #Integer, #Float, and #String. So if you have an arbitrary class (klass) that supports conversion from strings, you could do this:
Assuming a method named the same as klass is in Kernel. This RCR has these methods more appropriately as class methods of klass instead of in Kernel.
No problem. Fixnum inherits from Integer. Do you care whether Fixnum.from_s returns a Fixnum or a Bignum? It could return either just like many of the other Fixnum instance methods.
Good point. This RCR should to specify this. I would think it best if an exception occur if the full string doesn't parse the the target type. I'll change the implementation to use the methods that raise exceptions.
I haven't dealt with dates and times to know what all the options are. I threw this in at the last minute. If you don't like the klass.from_s method, you could define your own derived class (or override that klass.from_s):
and then make the default be a MyTime instead of a Time.
answered above
Eric
I don't think Integer.from_s(x) etc. are "methods more appropriately as class methods of klass instead of in Kernel". They are just not same. Besides that, sometimes functional form is more appealing than class method form.
- matz.
If you haven't noticed, I added an example that shows this RCR in action. I think this is the cleanest/most flexible solution with the given super-simple command line option spec: option => default (option value converted to default.class when given).
I see your example being neat. But I'm not sure it's enough to add from_s methods to the core to save a few lines of case statement.
In addition, I don't like the name "from_s".
- matz.
Fair enough. Maybe it'll go in some miscellaneous rubyforge project if it doesn't make it into std or built-in libs. I'm not at all tied to the name from_*. It just seemed to be a good complement to #to_*.
The concept of #from_s seems sound to me, from the standpoint of where methods belong. If you import an arbitrary class that can translate itself to/from a string, the class itself should have methods on it for that purpose, NOT muddy up the built-in string class to add them.
my_obj = MyClass.from_s( the_str ); my_obj.to_s
This should only exist in the case where a 1-1 correspondence exists between an instance of a class an its string representation. "foo bar" and "foo, bar" could both mean %w|foo bar|, so Array.from_s doesn't make sense, IMO.
Further, it makes sense to me that the set of core classes MAY cross the boundaries for simple interoperability. String#to_i makes sense, because String and Integer are both in the core.
In short - I think #from_s is a good pattern for your own custom classes, but doesn't need to be imposed on the core. It might be nice for consistency, but it wouldn't be consistent due to items like Array. - GavinKistner
I agree that Array.from_s doesn't make a lot of sense and I'm not proposing it.
Regarding naming, another option would be to put the word "new" in the name because it makes a new object of the class. Like perl's new_from_fd, it could be new_from_s. But, I still like simply from_s better.
A crazy idea (not putting it in the proposal) would be to make a default for this:
but you'd want to somehow make sure s didn't have any variable references in it (including indirectly to self).