Duck Typing and automated Conversions

R

Robert Klemme

Hi all,

conversion of method parameters seems to be a quite common scenario,
example:

def silly_example(str, count)
str = str.to_str
count = count.to_int

s = ""
count.times { s << str }
s
end

Usually these conversions are documented as requirements for method
parameters. We could make them a bit more explicit without changing much
especially not the power of Duck Typing. Here's an idea:

def silly_example(str.to_str, count.to_int)
# str and count are converted like in the example above
s = ""
count.times { s << str }
s
end

Basically this is just a way to save typing. Error reporting will be the
same. RDoc could evaluate this info and generate comments for this
automagically.

Of course, any method could be invoked like this, maybe even with
parameters. I haven't thouroughly thought about implications of that fact
yet.

Thoughs?

robert
 
P

Pit Capitain

Robert said:
def silly_example(str.to_str, count.to_int)
# str and count are converted like in the example above
s = ""
count.times { s << str }
s
end

Basically this is just a way to save typing. Error reporting will be the
same. RDoc could evaluate this info and generate comments for this
automagically.

Of course, any method could be invoked like this, maybe even with
parameters. I haven't thouroughly thought about implications of that fact
yet.

Thoughs?

Interesting idea. I think it's very readable.

Regards,
Pit
 
D

David A. Black

Hi --

Hi all,

conversion of method parameters seems to be a quite common scenario,
example:

def silly_example(str, count)
str = str.to_str
count = count.to_int

s = ""
count.times { s << str }
s
end

Usually these conversions are documented as requirements for method
parameters. We could make them a bit more explicit without changing much
especially not the power of Duck Typing. Here's an idea:

def silly_example(str.to_str, count.to_int)
# str and count are converted like in the example above
s = ""
count.times { s << str }
s
end

Basically this is just a way to save typing. Error reporting will be the
same. RDoc could evaluate this info and generate comments for this
automagically.

Of course, any method could be invoked like this, maybe even with
parameters. I haven't thouroughly thought about implications of that fact
yet.

Thoughs?

I personally don't like the '.' notation used for anything other than
method calls (and floats :) I just think it's too important and
central a notation to be borrowed for something else, even something
that is somewhat kindred to a method call (but isn't one).

There's also the question of the interaction of this with duck typing.
Of course that's always a programmer's choice, but I could imagine
cases where the caller would expect a duck-typing treatment and not
get it. For example:

obj = Object.new
def obj.times; 10; end
str = silly_example("hello", obj)

Here you have an object that responds to 'times', so it's capable of
being treated in a duck-typing way by simply being sent the message
'times' -- but if you intercept the object and do a to_int on it,
there's trouble.

(Of course you could document your method by saying that it requires
an object that responds to to_int, but that seems a bit roundabout
compared to just saying you want an object that responds to times.)


David
 
R

Robert Klemme

David A. Black said:
Hi --



I personally don't like the '.' notation used for anything other than
method calls (and floats :) I just think it's too important and
central a notation to be borrowed for something else, even something
that is somewhat kindred to a method call (but isn't one).

In fact the first character that came to mind was '#'. The char to use
here is not important to me. Here are some alternatives:

def silly_example(str.to_str, count.to_int)
def silly_example(str#to_str, count#to_int)
def silly_example(str:to_str, count:to_int)
def silly_example(str::to_str, count::to_int)
There's also the question of the interaction of this with duck typing.
Of course that's always a programmer's choice, but I could imagine
cases where the caller would expect a duck-typing treatment and not
get it. For example:

obj = Object.new
def obj.times; 10; end
str = silly_example("hello", obj)

Here you have an object that responds to 'times', so it's capable of
being treated in a duck-typing way by simply being sent the message
'times' -- but if you intercept the object and do a to_int on it,
there's trouble.

Well, of course. But in those cases you would not use that notation. You
would simply declare the parameter as you do now and write a comment that
this parameter must respond to #times.

Btw, did you purposely just return an int from your definition of times?
I mean, no block is invoked from that method.
(Of course you could document your method by saying that it requires
an object that responds to to_int, but that seems a bit roundabout
compared to just saying you want an object that responds to times.)

It's just another (supposedly shorter) form to write a common idiom. If
the writer of silly_example decides to do more complex treatment, he can
still do that. We don't loose anything. Note, that the creator of your
obj.times() would be similarly surprised if the implementor of
silly_example had choosen to do it exactly like I presented in the first
example.

I'm not sure whether I missed your point altogether. Could you please
elaborate? Thanks!

Kind regards

robert
 
I

Ilmari Heikkinen

Hi,

def silly_example(str.to_str, count.to_int)
# str and count are converted like in the example above
s = ""
count.times { s << str }
s
end

Not to argue against possible syntatic sugar, but you can already do
this in the method signature:

def silly_example(ostr, ocount, str=ostr.to_str, count=ocount.to_int)
 
R

Robert Klemme

Ilmari Heikkinen said:
Hi,



Not to argue against possible syntatic sugar, but you can already do
this in the method signature:

def silly_example(ostr, ocount, str=ostr.to_str, count=ocount.to_int)

Yeah, I know, but that's quite inconvenient IMHO. Also you need to
duplicate the number of your parameters plus it won't work with varying
lenght argument lists.

Regards

robert
 
D

David A. Black

Hi --

[I wrote:]

There's also the question of the interaction of this with duck typing.
Of course that's always a programmer's choice, but I could imagine
cases where the caller would expect a duck-typing treatment and not
get it. For example:

obj = Object.new
def obj.times; 10; end
str = silly_example("hello", obj)
[...]
Btw, did you purposely just return an int from your definition of times?
I mean, no block is invoked from that method.

I did it wrong. It should be:

def obj.times(&block)
10.times &block
end
It's just another (supposedly shorter) form to write a common idiom. If
the writer of silly_example decides to do more complex treatment, he can
still do that. We don't loose anything. Note, that the creator of your
obj.times() would be similarly surprised if the implementor of
silly_example had choosen to do it exactly like I presented in the first
example.

I'm not sure whether I missed your point altogether. Could you please
elaborate? Thanks!

Partly I'm just very conservative about special-case syntax and
shortcuts. I can't define exactly why, or exactly where I draw the
line, or whether there's even a line.... I think I'm also not
convinced that this (to_int etc.) is common enough or special enough
to warrant a change in method definitions semantics. I don't think I
can get much more concrete than that, so it may be that at some level
I don't have a point, rather than that you've missed it :)


David
 
D

David A. Black

Hi --

Hi,



Not to argue against possible syntatic sugar, but you can already do this in
the method signature:

def silly_example(ostr, ocount, str=ostr.to_str, count=ocount.to_int)

Yes except that then someone could call it like this:

silly_example("hi", 1, "hello", 2)

and clobber the conversions you were doing.


David
 
I

Ilmari Heikkinen

Hi,

Yes except that then someone could call it like this:

silly_example("hi", 1, "hello", 2)

and clobber the conversions you were doing.

Could argue that it's wanted behavior.. "if you want to hack this
without hacking to_str and to_int, replace the defaulted args"

Since this IMHO is pretty much an auto-documented wish for the caller
to supply objects that act like ducks.
 
G

gabriele renzi

Robert Klemme ha scritto:
Basically this is just a way to save typing. Error reporting will be the
same. RDoc could evaluate this info and generate comments for this
automagically.

Of course, any method could be invoked like this, maybe even with
parameters. I haven't thouroughly thought about implications of that fact
yet.

Thoughs?

Yes, one. See RCR 280, add this, and you get quite far in replicating
what mr van rossum is advocating as optional typing for python :) [1]
With pre/post method hook as supposed to be in Ruby2, you get even further.

[1]only the last post, the "stop flames" one would recall this, ignore
the other two.
 
R

Robert Klemme

David A. Black said:
Hi --

[I wrote:]

There's also the question of the interaction of this with duck typing.
Of course that's always a programmer's choice, but I could imagine
cases where the caller would expect a duck-typing treatment and not
get it. For example:

obj = Object.new
def obj.times; 10; end
str = silly_example("hello", obj)
[...]
Btw, did you purposely just return an int from your definition of times?
I mean, no block is invoked from that method.

I did it wrong. It should be:

def obj.times(&block)
10.times &block
end
It's just another (supposedly shorter) form to write a common idiom. If
the writer of silly_example decides to do more complex treatment, he can
still do that. We don't loose anything. Note, that the creator of your
obj.times() would be similarly surprised if the implementor of
silly_example had choosen to do it exactly like I presented in the first
example.

I'm not sure whether I missed your point altogether. Could you please
elaborate? Thanks!

Partly I'm just very conservative about special-case syntax and
shortcuts. I can't define exactly why, or exactly where I draw the
line, or whether there's even a line.... I think I'm also not
convinced that this (to_int etc.) is common enough or special enough
to warrant a change in method definitions semantics. I don't think I
can get much more concrete than that, so it may be that at some level
I don't have a point, rather than that you've missed it :)

Ah, ok, I see... :) Then let's see with what others come up. Maybe I've
orverlooked a complete showstopper and we can forget about this
immediately.

About usage of to_int et al: as these methods are fairly recent (compared
to to_i and others) I'd guess that usage is increasing and they might not
yet be as prolific as I assumed. Don't have exact numbers though.

Thanks for sharing your thoughts!

Kind regards

robert
 
D

David A. Black

Hi --

Hi,



Could argue that it's wanted behavior.. "if you want to hack this without
hacking to_str and to_int, replace the defaulted args"

Since this IMHO is pretty much an auto-documented wish for the caller to
supply objects that act like ducks.

You asked whether you could already do what Robert was proposing, by
using the construct you've suggested -- to which the answer is no,
since he his proposal didn't include that kind of overriding :)


David
 
D

David A. Black

Hi --

About usage of to_int et al: as these methods are fairly recent (compared
to to_i and others) I'd guess that usage is increasing and they might not
yet be as prolific as I assumed. Don't have exact numbers though.

I thought that their main purpose was to be defined on objects that
were expected to have to serve as strings or ints, etc. -- this kind
of thing:

obj = Object.new
def obj.to_str; "def"; end

def another_example(str)
"abc" << str
end

p another_example(obj) # "abcdef"

as opposed to being called explicitly, for which to_s and to_i would
serve more often. (Not that to_??? can't be called explicitly, of
course.)


David
 
R

Robert Klemme

gabriele renzi said:
Robert Klemme ha scritto:
Basically this is just a way to save typing. Error reporting will be the
same. RDoc could evaluate this info and generate comments for this
automagically.

Of course, any method could be invoked like this, maybe even with
parameters. I haven't thouroughly thought about implications of that fact
yet.

Thoughs?

Yes, one. See RCR 280, add this, and you get quite far in replicating
what mr van rossum is advocating as optional typing for python :) [1]
With pre/post method hook as supposed to be in Ruby2, you get even further.

[1]only the last post, the "stop flames" one would recall this, ignore
the other two.

Interesting. Thanks for pointing that out!

Although the two suggestions are clearly related and can be combined,
they're completely orthogonal IMHO. You can always have one without the
other. Personally I'm not sure whether I like the idea of a type
conversion framework like that of RCR 280 mainly because I'm wary about
having this functionality not in classes affected. You'll end up in a
situation where you can't rely on this because you neve know which
conversions are active. I't seems bad for lib designers. But that's
another discussion...

Kind regards

robert
 
G

gabriele renzi

Robert Klemme ha scritto:
Although the two suggestions are clearly related and can be combined,
they're completely orthogonal IMHO. You can always have one without the
other. Personally I'm not sure whether I like the idea of a type
conversion framework like that of RCR 280 [..]

Yes, I did not intend to say they implicate each other in some way, It
just came to my mind when I saw "any method could be invoked like this,
maybe even with parameters[..]".
 
T

trans. (T. Onoma)

*/, any other comments or suggestions,
| welcome!

I think this is the problem with your suggestion. You want to put a lot of
info into this little /*...*/. Have you considered what it might be like?

T.
 
R

Robert Klemme

itsme213 said:
I like this, and believe it can be generalized to be quite broadly useful.

I believe your signature tells a caller the following:
silly_example takes 2 params, x and y
(str and count, if you prefer; but see * below)
x must support #to_str, y must support #to_int
Correct.

silly_example will use them as follows:
str = x.to_str
count = y.to_int
Correct.

silly_example will not have other access to x, y
(*: since you did: str=str.to_str; count=count.to_int)

Wrong, because to_str need not create a new instance:
=> 135026792
There are 3 main properties about this signature:
1. it reveals the duck-type of x, y as required methods
Yep.

2. it reveals how those methods of x, y are used in the body
str = x.to_str
count = y.to_int
Yep.

3. it saves typing: in particular, it has "no extra cost", since it
automatically binds str and count and this does not have to be repeated
within the body of f().
Exactly.

What do you think about the following generalization of your idea? It will
be a bit of a jump, so *please* consider it carefully:

Allow a method signature to optionally indicate both
the duck type of a parameter (what methods it should
respond_to), as well as named variable bindings that
indicate the usage of those respond_to methods within
the method body.

I'll have to sleep over this. You'll get an answer later.

Kind regards

robert
 
M

Mark Hubbart

def silly_example(str.to_str, count.to_int)
# str and count are converted like in the example above
s = ""
count.times { s << str }
s
end

Basically this is just a way to save typing. Error reporting will be the
same. RDoc could evaluate this info and generate comments for this
automagically.

I could count on one hand the number of times I've converted arguments
at the top of the method. In those times, I consider it a bit of a
breakdown of duck-typing, not an extension of it.

Also, I agree with David about use of dot notation for this. This is,
in fact the reverse behavior from the usual dot notation; instead of
the expression returning the result of sending the message to the
object referenced by the variable, it references the variable to the
result of sending the message to the object.

However, I'm sure there are other alternate syntaxes that will work.
And, if you instead simply check that the passed object responds to
the selected message, that would be, imvho, an extension of
duck-typing....

def foo( str{to_str}, count{times} )
s = ""
count.times { s << str }
s
end

If foo is called with a str that doesn't respond_to?(to_str), there
should be an error raised. Perhaps a NoMethodError, or maybe a
TypeError, or maybe something new that means both.

I'm fond of doing things like this, especially for testing, and
sometimes for working with stuff in irb:

num, str = Object.new, Object.new

def num.times(&block) 23.times &block end
def str.to_str() "%02i_" % rand(99) end

foo num, str #==> "74_26_94_..."

I do this to make sure I'm allowing for maximum flexibility, or when
working with other's code, to find out what the actual requirements
are :)

There are a couple of problems still: First, an object might still be
able to respond to a message, using method_missing, but #respond_to?
won't show it. Second, I'm not sure about the syntax. It would work
(it currently give a parse error), but I think something better and
more self-explanatory could be found.

cheers,
Mark
 
R

Robert Klemme

itsme213 said:
The fact that String#to_str returns self, and Fixnum.to_int returns self,
is
purely coincidental aliasing (from inside your method body). The original
parm-list variables might as well be anonymous.

But it's aliasing nevertheless and thus the caller can't rely on that the
instances he provides remain unchanged thinking that the body of the method
never gets access to them. In fact, the aliasing might even be desired as
object creations are rather expensive and people are likely to provide
String instances where #to_str is needed.

Of course you can view "original arguments" as anonymous but what difference
would that make?

I don't seem to get your point with this. Can you attempt another
explanation?

kind regards

robert
 
R

Robert Klemme

Mark Hubbart said:
I could count on one hand the number of times I've converted arguments
at the top of the method. In those times, I consider it a bit of a
breakdown of duck-typing, not an extension of it.

Also, I agree with David about use of dot notation for this. This is,
in fact the reverse behavior from the usual dot notation; instead of
the expression returning the result of sending the message to the
object referenced by the variable, it references the variable to the
result of sending the message to the object.

However, I'm sure there are other alternate syntaxes that will work.
And, if you instead simply check that the passed object responds to
the selected message, that would be, imvho, an extension of
duck-typing....

def foo( str{to_str}, count{times} )
s = ""
count.times { s << str }
s
end

The syntax is not the problem. In fact I provided some alternatives and I
like your version, too.
If foo is called with a str that doesn't respond_to?(to_str), there
should be an error raised. Perhaps a NoMethodError, or maybe a
TypeError, or maybe something new that means both.

NoMethod error - same as in my first example (the way one would do it
today).
I'm fond of doing things like this, especially for testing, and
sometimes for working with stuff in irb:

num, str = Object.new, Object.new

def num.times(&block) 23.times &block end
def str.to_str() "%02i_" % rand(99) end

foo num, str #==> "74_26_94_..."

I do this to make sure I'm allowing for maximum flexibility, or when
working with other's code, to find out what the actual requirements
are :)

Well, then you'll learn that your instance must implement to_int. Same as
if someone had coded that directly in the method. I don't see this as an
argument against my suggested shortcut syntax, because that mainly affects
the *writer* of a method - not the *caller*.
There are a couple of problems still: First, an object might still be
able to respond to a message, using method_missing, but #respond_to?
won't show it.

You introduced the #respond_to? - my suggestion was to simply invoke it and
see what happens (duck typing).
Second, I'm not sure about the syntax. It would work
(it currently give a parse error), but I think something better and
more self-explanatory could be found.

Sure. I'm very open here. I think the selection of "." as separator was
not good. We can change that any time. But the real issue for me is, does
this make sense independend of syntax?

Kind regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,586
Members
45,097
Latest member
RayE496148

Latest Threads

Top