Idea: klass.from_s(str)

(Eric Mahurin) #1

I was thinking how in seems a little asymmetric that many
classes have #to_s method, but when you want to make one of
those objects from a String, you have to clutter the String
class with yet another #to_* method. So the String class gets
the burden of all those #to_* methods. Instead, what if we
would have the convention that creating an object from a string
would be a class method (for the class of that object) instead
of another String#to_* method. So, instead of these:

String#to_i
String#to_f
String#to_sym
String#to_<c> # where c is a abbreviation for the class

you'd have these:

Integer.from_s(str)
Float.from_s(str)
Symbol.from_s(str)
klass.from_s(str)

Maybe you'd even want to be able to convert from an inspect
string:

klass.from_inspect(str)

I'm not saying that we should replace all obj.to_ methods with
klass.from_ methods. I'm just saying to make them come in
pairs. If you have a klass#to_* method, you should have a
corresponding klass.from_* method if appropriate. This would
make your class more encapsulated, instead of having some of it
in String (or whatever class you are converting from).

···

__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com

(Nikolai Weibull) #2

Eric Mahurin wrote:

I was thinking how in seems a little asymmetric that many classes have
#to_s method, but when you want to make one of those objects from a
String, you have to clutter the String class with yet another #to_*
method. So the String class gets the burden of all those #to_*
methods. Instead, what if we would have the convention that creating
an object from a string would be a class method (for the class of that
object) instead of another String#to_* method. So, instead of these:

String#to_i String#to_f
String#to_sym
String#to_<c> # where c is a abbreviation for the class

you'd have these:

Integer.from_s(str)
Float.from_s(str)
Symbol.from_s(str)
klass.from_s(str)

How is

i = Integer.from_s("123")

better than

i = "123".to_i?

I prefer "send a message to the string to convert itself to something
else" over "send a message to a class to convert the argument to an
instance of that class",
        nikolai

klass.from_inspect(str)

Kind of like read in Haskell or similar in Lisp?,
        nikolai

···

--
Nikolai Weibull: now available free of charge at http://bitwi.se/!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

(David A. Black) #3

Hi --

I was thinking how in seems a little asymmetric that many
classes have #to_s method, but when you want to make one of
those objects from a String, you have to clutter the String
class with yet another #to_* method. So the String class gets
the burden of all those #to_* methods.

I don't see that as a burden. It seems to correspond precisely to
what's happening.

Instead, what if we
would have the convention that creating an object from a string
would be a class method (for the class of that object) instead
of another String#to_* method. So, instead of these:

String#to_i
String#to_f
String#to_sym
String#to_<c> # where c is a abbreviation for the class

you'd have these:

Integer.from_s(str)
Float.from_s(str)
Symbol.from_s(str)
klass.from_s(str)

That seems like at least an equal amount of clutter -- actually more,
since you've now got a receiver, method, and argument, where the
argument and the method name contain the same information (String-hood
in this case). It also introduces a whole range of scenarios
involving wrong arguments. I prefer simply sending a conversion
request to an object and getting its response.

Maybe you'd even want to be able to convert from an inspect
string:

klass.from_inspect(str)

I'm not saying that we should replace all obj.to_ methods with
klass.from_ methods. I'm just saying to make them come in
pairs. If you have a klass#to_* method, you should have a
corresponding klass.from_* method if appropriate. This would
make your class more encapsulated, instead of having some of it
in String (or whatever class you are converting from).

So you'd have:

Integer.from_f(1.0)
Integer.from_s("1")
Integer.from_nil(nil)
Integer.from_i(1)

instead of

1.0.to_i
"1".to_i
nil.to_i
1.to_i

I'm afraid it seems circuitous and verbose to me.

David

···

On Thu, 25 Aug 2005, Eric Mahurin wrote:

--
David A. Black
dblack@wobblini.net

(Joel VanderWerf) #4

Nikolai Weibull wrote:

i = Integer.from_s("123")

We already have something like that:

irb(main):001:0> Integer("123")
=> 123

That's better than #from_s becuse you don't have to specify the type of
the argument in the method name. It's like saying

Integer.from_whatever("123")

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(Eric Mahurin) #5

Maybe for many of the core classes, there should be both forms
if you are going from one core class to another.

The ugliness I think is when you are dealing with an arbitrary
class. You typically come up with an abbreviation for the
class and make a String#to_<abbrev> method convert a string to
an object of that class. You could have collisions with
another class using the same name.

The klass.from_s(str) form also offers a little more power. If
you are dealing with an arbitrary class (possibly of another
object) and you want to convert a String to an object of that
class, you just call the from_s method of that class. You
don't have to go figure out what the right String#to_* method
to call is based on the class (and violate duck typing).

You could also have klass#to_<coreAbbrev> and
klass.from_<coreAbbrev>(coreOjbect) methods for other core
classes or for that matter any known classes (i.e. yaml - y).

···

--- "David A. Black" <dblack@wobblini.net> wrote:

Hi --

On Thu, 25 Aug 2005, Eric Mahurin wrote:

> I was thinking how in seems a little asymmetric that many
> classes have #to_s method, but when you want to make one of
> those objects from a String, you have to clutter the String
> class with yet another #to_* method. So the String class
gets
> the burden of all those #to_* methods.

I don't see that as a burden. It seems to correspond
precisely to
what's happening.

> Instead, what if we
> would have the convention that creating an object from a
string
> would be a class method (for the class of that object)
instead
> of another String#to_* method. So, instead of these:
>
> String#to_i
> String#to_f
> String#to_sym
> String#to_<c> # where c is a abbreviation for the class
>
> you'd have these:
>
> Integer.from_s(str)
> Float.from_s(str)
> Symbol.from_s(str)
> klass.from_s(str)

That seems like at least an equal amount of clutter --
actually more,
since you've now got a receiver, method, and argument, where
the
argument and the method name contain the same information
(String-hood
in this case). It also introduces a whole range of scenarios
involving wrong arguments. I prefer simply sending a
conversion
request to an object and getting its response.

> Maybe you'd even want to be able to convert from an inspect
> string:
>
> klass.from_inspect(str)
>
> I'm not saying that we should replace all obj.to_ methods
with
> klass.from_ methods. I'm just saying to make them come in
> pairs. If you have a klass#to_* method, you should have a
> corresponding klass.from_* method if appropriate. This
would
> make your class more encapsulated, instead of having some
of it
> in String (or whatever class you are converting from).

So you'd have:

Integer.from_f(1.0)
Integer.from_s("1")
Integer.from_nil(nil)
Integer.from_i(1)

instead of

1.0.to_i
"1".to_i
nil.to_i
1.to_i

I'm afraid it seems circuitous and verbose to me.

____________________________________________________
Start your day with Yahoo! - make it your home page

(Nikolai Weibull) #6

Joel VanderWerf wrote:

Nikolai Weibull wrote:

> i = Integer.from_s("123")

We already have something like that:

irb(main):001:0> Integer("123")
=> 123

Yes, but it's a function and it doesn't really do very much more than
#to_i (#to_int when possible) would do,
        nikolai

···

--
Nikolai Weibull: now available free of charge at http://bitwi.se/!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

(David A. Black) #7

Hi --

I wrote:

So you'd have:

Integer.from_f(1.0)
Integer.from_s("1")
Integer.from_nil(nil)
Integer.from_i(1)

instead of

1.0.to_i
"1".to_i
nil.to_i
1.to_i

I'm afraid it seems circuitous and verbose to me.

Maybe for many of the core classes, there should be both forms
if you are going from one core class to another.

I still prefer the technique of asking an object to provide, if
possible, a conversion of itself.

The ugliness I think is when you are dealing with an arbitrary
class. You typically come up with an abbreviation for the
class and make a String#to_<abbrev> method convert a string to
an object of that class. You could have collisions with
another class using the same name.

Is that a widespread technique? (I don't believe I've ever done it.)
If you do it, you'd want to do it more safely, perhaps by extending
individual objects.

The klass.from_s(str) form also offers a little more power. If
you are dealing with an arbitrary class (possibly of another
object) and you want to convert a String to an object of that
class, you just call the from_s method of that class. You
don't have to go figure out what the right String#to_* method
to call is based on the class (and violate duck typing).

Duck typing is irrelevant here. In both of these cases:

   String.to_i
   Integer.from_s(str)

you're depending on class membership and a close class/type
correspondence at the level of the core classes. It's not even really
the kind of scenario that duck typing pertains to. If you want to be
utterly doctrinaire about never acknowledging the existence of any
class, then you have to avoid *all* of this; you can't just convert in
the abstract. But I believe the conversion methods have proven their
usefulness, and their ability to exist peacefully in a
quasi-prototype-based environment.

Conversion based more closely on that quasi-prototype ideal is
actually handled by extending objects, module-wise or method by
method. In other words, if you want to convert obj into "an object
that can do <x>", then you add the x capability to obj. That's fine,
but it's not the same as any version of the hard-coded (as to class)
things we've been talking about.

David

···

On Fri, 26 Aug 2005, Eric Mahurin wrote:

--
David A. Black
dblack@wobblini.net

(Nikolai Weibull) #8

Eric Mahurin wrote:

The ugliness I think is when you are dealing with an arbitrary
class. You typically come up with an abbreviation for the
class and make a String#to_<abbrev> method convert a string to
an object of that class. You could have collisions with
another class using the same name.

Well, considering that there are only two methods in String by default:

irb(main):003:0> String.methods.grep(/^to_/).sort
=> ["to_a", "to_s"],

I really don't see the problem. How many classes need to be able to be
created from a String anyway?,
        nikolai

P.S.
OK, the following is kind of ugly when you think about it:

irb(main):004:0> require 'yaml'
=> true
irb(main):005:0> String.methods.grep(/^to_/).sort
=> ["to_a", "to_s", "to_yaml", "to_yaml_properties", "to_yaml_type"]
D.S.

···

--
Nikolai Weibull: now available free of charge at http://bitwi.se/!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

(Eric Mahurin) #9

I think an example is needed to demonstrate my point. Say you
have a command line parser where you want to convert options
from String to the relavent class. Somewhere in the
specification, you may specify the class that you want the
option to be. With the String#to_* methods, you have to do
something like this:

def parse_option(klass,option)
  if klass.equal?(String)
    option
  elsif klass.equal?(Integer)
    option.to_i
  elsif klass.equal?(Float)
    option.to_f
  else
    raise("can't convert #{option} to #{klass}")
  end
end

With klass.from_s(str) methods, here is what you get:

def parse_option(klass,option)
  klass.from_s(option)
end

Which is duck-typed?

And like I said, in another message, we can still have this to
add more convienence:

class String
  def to(klass)
    klass.from_s(self)
  end
end

···

--- "David A. Black" <dblack@wobblini.net> wrote:

> The klass.from_s(str) form also offers a little more power.
If
> you are dealing with an arbitrary class (possibly of
another
> object) and you want to convert a String to an object of
that
> class, you just call the from_s method of that class. You
> don't have to go figure out what the right String#to_*
method
> to call is based on the class (and violate duck typing).

Duck typing is irrelevant here.

____________________________________________________
Start your day with Yahoo! - make it your home page

(James Edward Gray II) #10

??? ri shows me to_f, to_i, to_s, to_str, and to_sym.

James Edward Gray II

···

On Aug 25, 2005, at 11:37 AM, Nikolai Weibull wrote:

Well, considering that there are only two methods in String by default:

irb(main):003:0> String.methods.grep(/^to_/).sort
=> ["to_a", "to_s"]

(ts) #11

??? ri shows me to_f, to_i, to_s, to_str, and to_sym.

moulon% ruby -e 'p String.instance_methods(false).grep(/^to_/).sort'
["to_f", "to_i", "to_s", "to_str", "to_sym"]
moulon%

Guy Decoux

(David A. Black) #12

Hi --

The klass.from_s(str) form also offers a little more power.

If

you are dealing with an arbitrary class (possibly of

another

object) and you want to convert a String to an object of

that

class, you just call the from_s method of that class. You
don't have to go figure out what the right String#to_*

method

to call is based on the class (and violate duck typing).

Duck typing is irrelevant here.

I think an example is needed to demonstrate my point. Say you
have a command line parser where you want to convert options
from String to the relavent class. Somewhere in the
specification, you may specify the class that you want the
option to be. With the String#to_* methods, you have to do
something like this:

def parse_option(klass,option)
if klass.equal?(String)
   option
elsif klass.equal?(Integer)
   option.to_i
elsif klass.equal?(Float)
   option.to_f
else
   raise("can't convert #{option} to #{klass}")
end
end

With klass.from_s(str) methods, here is what you get:

def parse_option(klass,option)
klass.from_s(option)
end

Which is duck-typed?

Neither of them would I include in, say, an essay, on duck typing, not
because they "violate" it (it's not a rule) but because it's not
relevant. Once you're passing class names around and doing explicit
conversions based on class, that's what you're doing, and you might as
well embrace it.

In the case of your example, I would probably do it like this:

   def parse_option(option)
     #...
   end

   parse_option(3)
   parse_option(str.to_f)
   parse_option(MyClass.from_s(str)) # if you must :slight_smile:

etc. -- and then you can duck-type the incoming argument all you like
without having to convert it. It's very cumbersome to have a method
whose arguments have to be: an object, and a class which responds to
from_s and whose from_s method takes objects of the class of *this*
object as arguments.... Better to do it before you call the method.

David

···

On Fri, 26 Aug 2005, Eric Mahurin wrote:

--- "David A. Black" <dblack@wobblini.net> wrote:

--
David A. Black
dblack@wobblini.net

(Daniel Brockman) #13

Eric,

With the String#to_* methods, you have to do something
like this:

def parse_option(klass,option)
  if klass.equal?(String)
    option
  elsif klass.equal?(Integer)
    option.to_i
  elsif klass.equal?(Float)
    option.to_f
  else
    raise("can't convert #{option} to #{klass}")
  end
end

With klass.from_s(str) methods, here is what you get:

def parse_option(klass,option)
  klass.from_s(option)
end

Which is duck-typed?

As David said, duck typing is irrelevant in these examples.

Duck typing is where you don't care about what class (Duck,
WalkingQuackMachine) some given object is an instance of,
but rather what messages (walk, quack) it responds to.

How do you map that to the `parse_option' example?

I'd describe the difference between your two snippets by
saying that the former uses *external* polymorphism (which
is widely considered bad OO design) while the latter uses
*internal* polymorphism (essential to good OO design).

Kind regards,

···

--
Daniel Brockman <daniel@brockman.se>

(Nikolai Weibull) #14

James Edward Gray II wrote:

···

On Aug 25, 2005, at 11:37 AM, Nikolai Weibull wrote:

> Well, considering that there are only two methods in String by
> default:
>
> irb(main):003:0> String.methods.grep(/^to_/).sort
> => ["to_a", "to_s"]

??? ri shows me to_f, to_i, to_s, to_str, and to_sym.

Oops, I should have written String.new.methods....,
        nikolai

--
Nikolai Weibull: now available free of charge at http://bitwi.se/!
Born in Chicago, IL USA; currently residing in Gothenburg, Sweden.
main(){printf(&linux["\021%six\012\0"],(linux)["have"]+"fun"-97);}

(Eric Mahurin) #15

>
>>> The klass.from_s(str) form also offers a little more
power.
>> If
>>> you are dealing with an arbitrary class (possibly of
>> another
>>> object) and you want to convert a String to an object of
>> that
>>> class, you just call the from_s method of that class.
You
>>> don't have to go figure out what the right String#to_*
>> method
>>> to call is based on the class (and violate duck typing).
>>
>> Duck typing is irrelevant here.
>
> I think an example is needed to demonstrate my point. Say
you
> have a command line parser where you want to convert
options
> from String to the relavent class. Somewhere in the
> specification, you may specify the class that you want the
> option to be. With the String#to_* methods, you have to do
> something like this:
>
> def parse_option(klass,option)
> if klass.equal?(String)
> option
> elsif klass.equal?(Integer)
> option.to_i
> elsif klass.equal?(Float)
> option.to_f
> else
> raise("can't convert #{option} to #{klass}")
> end
> end
>
> With klass.from_s(str) methods, here is what you get:
>
> def parse_option(klass,option)
> klass.from_s(option)
> end
>
> Which is duck-typed?

Neither of them would I include in, say, an essay, on duck
typing, not
because they "violate" it (it's not a rule) but because it's
not
relevant. Once you're passing class names around and doing
explicit
conversions based on class, that's what you're doing, and you
might as
well embrace it.

I'd disagree. Even if you are passing classes around you
should be able to apply duck typing if you have the right
infrastructure. The class just has to respond to the right
methods as opposed to the object.

In the case of your example, I would probably do it like
this:

   def parse_option(option)
     #...
   end

   parse_option(3)
   parse_option(str.to_f)
   parse_option(MyClass.from_s(str)) # if you must :slight_smile:

etc. -- and then you can duck-type the incoming argument all
you like
without having to convert it. It's very cumbersome to have a
method
whose arguments have to be: an object, and a class which
responds to
from_s and whose from_s method takes objects of the class of
*this*
object as arguments.... Better to do it before you call the
method.

Let's make a class out of this - an option spec:

class Option
  def initialize(name,required=false,klass=String)
    @name = name
    @required = required
    @klass = klass
  end
  ...
  def parse_option_arg(arg)
    @klass.from_s(arg)
  end
end

So, when you are describing the command line, you might give it
a list of these option specs:

Option.new("n",true,Integer),
Option.new("ratio",true,Float),
Option.new("f",true,File), # File#from_s(str) will open a File

Then when you have a command line like this:

-n 4 -ratio 0.75 -f xyz.txt

it could easily create objects appropriate for each option.

···

--- "David A. Black" <dblack@wobblini.net> wrote:

On Fri, 26 Aug 2005, Eric Mahurin wrote:
> --- "David A. Black" <dblack@wobblini.net> wrote:

____________________________________________________
Start your day with Yahoo! - make it your home page

(Eric Mahurin) #16

Eric,

> With the String#to_* methods, you have to do something
> like this:
>
> def parse_option(klass,option)
> if klass.equal?(String)
> option
> elsif klass.equal?(Integer)
> option.to_i
> elsif klass.equal?(Float)
> option.to_f
> else
> raise("can't convert #{option} to #{klass}")
> end
> end
>
> With klass.from_s(str) methods, here is what you get:
>
> def parse_option(klass,option)
> klass.from_s(option)
> end
>
> Which is duck-typed?

As David said, duck typing is irrelevant in these examples.

Duck typing is where you don't care about what class (Duck,
WalkingQuackMachine) some given object is an instance of,
but rather what messages (walk, quack) it responds to.

You are correct with that definition. Which admittedly is the
common and original definition. I was thinking of a slightly
different definition - don't compare a class (likely from an
object) against another specfic class.

For example, is this considered good duck typing?

klass = obj.class
obj2 = klass.new

vs. this:

klass = obj.class
if klass.equal?(String)
...
end

Maybe with the the original definition, neither of these are
duck-typed because you are using Object#class. But, in the
spirit of duck typing, I'd say the first is good and the second
not duck-typed.

How do you map that to the `parse_option' example?

I'd describe the difference between your two snippets by
saying that the former uses *external* polymorphism (which
is widely considered bad OO design) while the latter uses
*internal* polymorphism (essential to good OO design).

I guess whatever you want to call it you'd agree that the
second example is better.

···

--- Daniel Brockman <daniel@brockman.se> wrote:

____________________________________________________
Start your day with Yahoo! - make it your home page

(David A. Black) #17

Hi --

def parse_option(klass,option)
if klass.equal?(String)
   option
elsif klass.equal?(Integer)
   option.to_i
elsif klass.equal?(Float)
   option.to_f
else
   raise("can't convert #{option} to #{klass}")
end
end

With klass.from_s(str) methods, here is what you get:

def parse_option(klass,option)
klass.from_s(option)
end

Which is duck-typed?

Neither of them would I include in, say, an essay, on duck
typing, not
because they "violate" it (it's not a rule) but because it's
not
relevant. Once you're passing class names around and doing
explicit
conversions based on class, that's what you're doing, and you
might as
well embrace it.

I'd disagree. Even if you are passing classes around you
should be able to apply duck typing if you have the right
infrastructure. The class just has to respond to the right
methods as opposed to the object.

The class *is* the object, in this case. But that's not my point. I
just think there's no point in concealing the conversion code, in
order to go through a kind of pseudo-polymorphic layer.

Let's make a class out of this - an option spec:

class Option
def initialize(name,required=false,klass=String)
   @name = name
   @required = required
   @klass = klass
end
...
def parse_option_arg(arg)
   @klass.from_s(arg)
end
end

So, when you are describing the command line, you might give it
a list of these option specs:

Option.new("n",true,Integer),
Option.new("ratio",true,Float),
Option.new("f",true,File), # File#from_s(str) will open a File

                               (File.from_s?)

In what mode? Or will it write the string to a file? I don't have
any instinctive sense of what File.from_s would or should do.

That's part of the problem for me: I don't think there should be a
generalized conversion mechanism because I'm not convinced that
class-to-class conversions (whether lexically visible as such, or
stashed one method definition away) really are a generality.

David

···

On Fri, 26 Aug 2005, Eric Mahurin wrote:

--- "David A. Black" <dblack@wobblini.net> wrote:

On Fri, 26 Aug 2005, Eric Mahurin wrote:

--
David A. Black
dblack@wobblini.net

(David A. Black) #18

Hi --

For example, is this considered good duck typing?

klass = obj.class
obj2 = klass.new

vs. this:

klass = obj.class
if klass.equal?(String)
...
end

Maybe with the the original definition, neither of these are
duck-typed because you are using Object#class. But, in the
spirit of duck typing, I'd say the first is good and the second
not duck-typed.

You're shaving the concept of duck typing down to a trivial level,
while also exalting it to the status of the only important principle
in Ruby programming.

It is neither. It is, in what I believe are the exact words of Dave
Thomas, "a way of thinking about Ruby programming." It's not a game
of "Simon Says" ("You called #class -- you're out!") Try to see the
broad picture :slight_smile:

David

···

On Fri, 26 Aug 2005, Eric Mahurin wrote:

--
David A. Black
dblack@wobblini.net