Gedankenexperiment on method duck type safety

T

Tim Connor

*braces for the flames to follow*

I had a strange idea this morning, namely, how would I merge the idea
of the contractual nature of static type safety, with the flexibility
of duck-typing. I know, it's a hot button, and I've read various
other threads that come close, but don't quite address it the same
way.

And I am not seriously proposing this as a "good idea" or anything. I
was just thinking "hmm, is there any way to get ducktype safety,
without manually throwing ParameterErrors in the header of the method
if something fails a responds_to". Yes, there are times you want more
complex handling if something doesn't quack, but sometimes you do want
to enforce that, and right now it is just a bunch of extra ifs and
unlesses.

And I know this doesn't fit with he already complex enough method
declarations in ruby. It's just faux ruby style, because that is my
favorite dynamic language. So, without further prevarication, here is
the aforementioned abomination, for either discussion sake, or
pointing and laughing, whichever floats your boat:

def foo(bar.is_? Array, !bar.is_empty?, baz.responds_to :webbed_feet)
 
E

Eivind Eklund

And I know this doesn't fit with he already complex enough method
declarations in ruby. It's just faux ruby style, because that is my
favorite dynamic language. So, without further prevarication, here is
the aforementioned abomination, for either discussion sake, or
pointing and laughing, whichever floats your boat:

def foo(bar.is_? Array, !bar.is_empty?, baz.responds_to :webbed_feet)

See code implementing something similar at
http://people.freebsd.org/~eivind/ruby/types/

typesig Type::And(Array, Type::RespondFalse:)is_empty?)),
Type::RespondTo:)webbed_feet)
def foo(bar, baz)
...
end

You'd have to implement Type::RespondFalse - should be trivial.

Eivind.
 
T

Tim Connor

See code implementing something similar athttp://people.freebsd.org/~eivind/ruby/types/

I like your warning in the readme: http://people.freebsd.org/~eivind/ruby/types/README
and I wouldn't be surprised to find that being the case. I was
considering though if a less verbose approach would be more rubyish
and less painful. None of the extra pre-declaration required in the
usual type system, but just how to make method declarations more
explicitly contractual (looking at the contractual aspect more than
the type system aspect). I suspect it is still a case of YAGNI, as I
haven't really needed it yet, too much, but there are just some odd,
harder than they should be bugs I've had to trace.

Yeah tests and BDD helps a lot, and maybe a convention of always
testing the nil and edge cases for each parameter. But when something
nested goes awry, in a situation you know exactly what the function
wants (even if it's just a responds_to), it might be nice to get a
more immediate failure. Maybe that would interfere with refactoring
and lead to a more Javaesque inflexibility, though. I dunno, to be
honest. Which is why I posted. ;)

Apparently in your experience it just led to additional pain with
little benefit?
 
R

Robert Klemme

I had a strange idea this morning, namely, how would I merge the idea
of the contractual nature of static type safety, with the flexibility
of duck-typing. I know, it's a hot button, and I've read various
other threads that come close, but don't quite address it the same
way.

And I am not seriously proposing this as a "good idea" or anything. I
was just thinking "hmm, is there any way to get ducktype safety,
without manually throwing ParameterErrors in the header of the method
if something fails a responds_to".

I tend to believe that this approach is at least ill named - if not
worse and here's why: If you test with respond_to? you are no longer
duck typing. If all you test is respond_to? then you gain nothing over
normal interpreter execution, exceptions are just thrown a bit earlier -
but at the price of more complex code or method declarations and runtime
overhead. Also, it's not _static_ type safety.
Yes, there are times you want more
complex handling if something doesn't quack, but sometimes you do want
to enforce that, and right now it is just a bunch of extra ifs and
unlesses.

Well, the interpreter will enforce it if methods are called that do not
exist on those objects.
And I know this doesn't fit with he already complex enough method
declarations in ruby.

Um, where is declaring methods in Ruby complex? I haven't noticed so far.
It's just faux ruby style, because that is my
favorite dynamic language. So, without further prevarication, here is
the aforementioned abomination, for either discussion sake, or
pointing and laughing, whichever floats your boat:

def foo(bar.is_? Array, !bar.is_empty?, baz.responds_to :webbed_feet)

You can certainly squeeze this into Ruby - Ruby is so flexible and a lot
of people have done all sorts of weird things with the language. I
would not bother a second to seriously consider this...

Kind regards

robert
 
D

Daniel DeLorme

Tim said:
*braces for the flames to follow*

I had a strange idea this morning, namely, how would I merge the idea
of the contractual nature of static type safety, with the flexibility
of duck-typing. I know, it's a hot button, and I've read various
other threads that come close, but don't quite address it the same
way.

def foo(bar.is_? Array, !bar.is_empty?, baz.responds_to :webbed_feet)

A lot of other people have gone down this road, but patching "static"
type checking into a dynamic language is IMHO futile. However I think
something interesting could be done with a static/compiled dialect of
ruby where method signatures are inferred from the method body. e.g.:

#this method requires an argument that responds to to_str
def foo(a)
a.to_str
end
foo("str") #compiles ok
foo(42) #compile error

you could also do method overloading:

def foo(a)
a.to_str
end
def foo(a)
a.to_ary
end

"static duck typing"
It's fun to think about, but soooo far beyond my skills ;_;

Daniel
 
R

Robert Klemme

2008/2/1 said:
A lot of other people have gone down this road, but patching "static"
type checking into a dynamic language is IMHO futile.
Right.

However I think
something interesting could be done with a static/compiled dialect of
ruby where method signatures are inferred from the method body. e.g.:

And how do you ensure only code compiles that satisfies this static
constraint? How do you make sure that only objects are passed that
satisfy this constraint? Basically this can only be done if you
statically explore all (and I mean *all*) possible execution paths of
a program. I believe this is impossible, especially in light of
compiled C extensions.
It's fun to think about, but soooo far beyond my skills ;_;

Probably beyond *anybody's* skills in the case of Ruby. :)

Kind regards

robert
 
T

Thomas Wieczorek

Though I think that is_a? should be used sparsely. I like the idea of
gradual typing: Develop your program first without type definitions
and then when you think some regions need speed up, add the used types
of the objects.
 
T

Tim Connor

"static duck typing"
It's fun to think about

Yeah, that was my main point all along; to see what I'd learn about
good practices, from the responses, if nothing else. The point that
the interpreter does throw an error eventually, so why worry about
making it earlier, for instance, is not entirely invalid.

I still think you can get weird bugs (hard to find or account for ones
that only surface on the missed corner case every so often) based on
violation of contract, if you don't remember to test in your method
preface/header each and every time. I mean, really who hasn't been
bitten by a bug that took some digging that had to do with a parameter
not meeting some of their assumptions?

On the other, I do love the grace, ease, and verbosity of ruby's loose
typing. If I ever have to go back to a statically typed language,
particularly one as verbose about it has Java or C#, it's going to
hurt.
 
R

Rick DeNatale

I mean, really who hasn't been
bitten by a bug that took some digging that had to do with a parameter
not meeting some of their assumptions?

Yes, and that happens with statically typed languages, actually those
bugs tend to be harder to diagnose with static languages.

Most of the really awful things which can happen in statically typed
languages which can be caught by static type checking are caused by
the fact that the compiled code relies on static types. For example
in C++, it would be really bad if a member of an object was 'accessed'
when the object was of the wrong type, since accessing is done by
early (compile-time) bound offsets. Dynamically typed languages
trade off a little bit of run-time performance for safety or at least
serviceability by deferring such bindings to run-time and checking
them.

Other very common 'type' mistakes such as array bounds errors aren't
caught by most statically typed language compilers, yes some advanced
type systems attack this problem but it's still considered an advanced
type-theory issue in practice. So most statically typed languages
defer bounds checking to run-time if they do it at all.

Back in the days when the arguments raged about C+ vs. Smalltalk,
Bjarne Stroustrup was wont to say that he didn't want to fly in a
plane where the autopilot could throw a does_not_understand exception,
my feeling that that would be better than the autopilot branching to a
virtual function through a corrupted or type-punned pointer.
 
E

Eivind Eklund

I like your warning in the readme: http://people.freebsd.org/~eivind/ruby/types/README
and I wouldn't be surprised to find that being the case. I was
considering though if a less verbose approach would be more rubyish
and less painful. None of the extra pre-declaration required in the
usual type system, but just how to make method declarations more
explicitly contractual (looking at the contractual aspect more than
the type system aspect). I suspect it is still a case of YAGNI, as I
haven't really needed it yet, too much, but there are just some odd,
harder than they should be bugs I've had to trace.

Yeah tests and BDD helps a lot, and maybe a convention of always
testing the nil and edge cases for each parameter. But when something
nested goes awry, in a situation you know exactly what the function
wants (even if it's just a responds_to), it might be nice to get a
more immediate failure. Maybe that would interfere with refactoring
and lead to a more Javaesque inflexibility, though. I dunno, to be
honest. Which is why I posted. ;)

Apparently in your experience it just led to additional pain with
little benefit?

Yes, though this may be due to me not having learned when to apply
this and not - I've only tested it out for fairly small projects.

Probably the core problem in how I used it was that I added checks
"all over the place". This got in the way of refactoring; in quite a
bit of code, things are passed through that are used elsewhere, and
when I added more checks for this, it was no longer trivial to change
the type of the pass-through parameter.

For instance, I was working on a firewall maintenance application, and
at some point I wanted to change from an IP address to a rule number
(or vice versa, I don't remember) as an identifier for what to
manipulate. Without checks, it was a three line change - change at
the original originator of the ID, change at the two consumers. With
type checks, I ended up needing to modify things all over the place.

The mistake, of course, was that I'd been using more concrete types
than I should at the point where I declared the checks - but - this is
a thinking shortcut that I think we all do at times. I was thinking
of that IP address as an IP address, not as an identifier for
something that could be identified another way, and it was natural to
check for what I was actually dealing with.

It is possible that conservative application of the type checking
library could be useful. I have, for instance, used it to add checks
to a hash to enforce that only Integers were inserted/indexed into it,
because I was processing data that both came from a database and from
text files, and wanted to make sure that the same kind identifier was
used (ie, that I didn't get a string "1234" from the text file and
tried to use it as an identifier, checking to find the record 1234).
Adding this check to the hash was useful - it was part of debugging
process, and meant that I could be sure that the bugs I was seeing at
least didn't come from that particular error.


Apart from that, I wonder if it might be useful to add checks at the
"edges of libraries" - the public APIs that people consume. This
would both function as very formal documentation and trap errors in
cases where they are more likely to occur.

Eivind.
 
S

stephenT

The first thing that comes to mind, and this may say more about the
oddities of my mind and lack of any real cs training, is that part of
what you want is, that once you've identified a duck you(or the
runtime system) want to 'know' about ducks. If you have another animal
you want to be able to:
1. ask is this a duck - does it do the same things that the known
duck does?
2. you may want to 'know' that this is also a duck - automatic
subclassing and extraction of commonalities.
3. you may want to get a duck and do something with it (last time I
caught a duck a took it into a bar just to see what would happen)

I guess you could extend the base object to include a 'type manager'
to which you could add type identified instances and build,
dynamically, the meaning of a particular type from the instances. You
could also validate or test instances against your types - imagine
getting the response 90% confident that it's a duck, 60% confident
that it's a chicken, 1% dog.

Slightly askew of the point: In the run time context is it more
important to know if 'this' thing quacks and then get it to quack
(point to point message) or do you just need something(s) to quack
(broadcast message). Not more important in general, but this might be
an important questions to ask in the initial design.
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,062
Latest member
OrderKetozenseACV

Latest Threads

Top