[ANN] Ducktator - A Duck Type Validator

O

Ola Bini

Austin said:
Additionally, if it's an external validation suite -- I haven't looked
at the project -- it isn't really checking against live code.

Why couldn't it check against live code?

I receive an argument foo,
I check foo against a validator,
I do an operation on foo, if foo is valid:

def abc(foo)
do_operation(foo) if Ducktator.valid?(validator, foo)
end

which part isn't live code?

--
Ola Bini (http://ola-bini.blogspot.com)
JvYAML, RbYAML, JRuby and Jatha contributor
System Developer, Karolinska Institutet (http://www.ki.se)
OLogix Consulting (http://www.ologix.com)

"Yields falsehood when quined" yields falsehood when quined.
 
M

MonkeeSage

By asking if object O respond_to? method M, we can impose certain
conditions, and raise an error if those condition aren't met, rather
than raising a runtime error in the middle of the program or getting
wonky behavior. I.e., think of it as pre-emptive TDD. This isn't
_necessary_ for duck-typing, but it certainly seems to be an extension
of it. Do you want your object, O, to act like a file? Then you better
make sure that it respond_to? read() and write()!

Regards.
Jordan
 
S

Simen Edvardsen

I beg to differ, and according to the pick-axe, this interpretation is
correct. Duck-typing isn't to *trust* your callers. Duck typing is to
check if something quacks like a duck and walks like a duck. Well, me
calling respond_to? checks if the object quacks and walks. What you are
describing isn't a technique, it is the absence of a technique.

The problem with #respond_to? is that it only tells you that a method
is supported. It doesn't tell you whether the method takes arguments
that quack like the ones you want to pass, or that the return value
behaves what you like. In other words, you'll have to trust that the
method behaves how you'd like to.

Imagine:

def make_peace(world)
world.create_piece if world.respond_to? :create_piece
end

class World
def make_piece(means = War)
means.use! self
end
end

Now make_peace (called with a World) will actually start a war. See
the problem? #respond_to? will only tell you that a method is
supported, but not that it behaves how you'd like (not that static
typing guarantees this either). It saves you from NoMethodError, but
not any other error.

Duck typing is trusting other objects to do the right thing, and
#respond_to? isn't all that useful in that regard.
 
R

Robert Klemme

I beg to differ, and according to the pick-axe, this interpretation is
correct. Duck-typing isn't to *trust* your callers. Duck typing is to
check if something quacks like a duck and walks like a duck.

Definitively not. What do you do if that method returns false?
Basically you can only raise an exception. Of course, you can use a
different exception type and message but that's it.

If you need respond_to? to choose a different path of execution because
a method needs to cope with different types that's ok but it's not duck
typing.

Regards

robert
 
J

James Edward Gray II

The problem with #respond_to? is that it only tells you that a method
is supported.

It doesn't even reliably tell you that:
=> false

James Edward Gray II
 
M

MonkeeSage

Of course, respond_to? doesn't tell you about what the method does. But
it isn't meant to. To check an object's signature is not the same thing
as to check that it does what you want it to do. A file object that
responds to write() may output a file in the current working directory;
but a mock object which is _acting like_ a file object may just write
to screen, or may not write anything at all. But at least you can tell
that it _acts like_ a file object so far as its signature is concerned
(viz., duck typing).

Regards,
Jordan
 
A

Austin Ziegler

I beg to differ, and according to the pick-axe, this interpretation is
correct. Duck-typing isn't to *trust* your callers. Duck typing is to
check if something quacks like a duck and walks like a duck. Well, me
calling respond_to? checks if the object quacks and walks. What you are
describing isn't a technique, it is the absence of a technique.

You're incorrect here. I do not need to check if someone responds to
#<< if I want to call #<< on an object. I simply call #<<. I then
*document* that I'm calling #<< on a received object and that if you
don't implement #<< in a sensible way, your code is going to break.

I trust my callers not to be stupid and send me an object that doesn't
implement #<< in this case. Checking for #<< would be the height of
stupidity.

Duck typing is treating an object like the type of object you need it
to be. It's a technique, not an absence. It's learning to let go of
the false sense of security that static typing gives people (and it
*is* a false sense of security). In my early days with Ruby, I
implemented #method_missing on a few things but didn't implement
#respond_to? on those same things. My code would have probably worked
with my style of duck typing, but would definitely not pass
validation.

Method presence validation is really useful during testing, but is of
limited use outside of it. (However, I have used it for other things
to add methods dynamically to classes, too.)

-austin
 
A

Austin Ziegler

Why couldn't it check against live code?

I snipped context, but I explicitly said "if it's an external
validation suite." Based on your example (also snipped), it's not an
external validation suite.

I doubt that I'd use it, but I think that this may have some value. It
is not, however, part of what I consider to be duck typing. It's
contract enforcement.

-austin
 
A

Austin Ziegler

By asking if object O respond_to? method M, we can impose certain
conditions, and raise an error if those condition aren't met, rather
than raising a runtime error in the middle of the program or getting
wonky behavior. I.e., think of it as pre-emptive TDD. This isn't
_necessary_ for duck-typing, but it certainly seems to be an extension
of it. Do you want your object, O, to act like a file? Then you better
make sure that it respond_to? read() and write()!

No, you better not. If I use #<<, then I can "write" to a String, a
StringIO, an IO (socket, file, etc.), or an Array. Checking for #write
limits you to (mostly) actual IO objects. Reading is a little harder,
but I tend not to bother checking for that -- I generally just call
#read and tell people in the API that #read will be called at some point
and it better return something useful.

It isn't preemptive TDD to do validation, and in Ruby ALL errors are
runtime. Method presence validation is useful and sometimes necessary;
if you look at Text::Format, you'll see that I do both method presence
and arity validation for hyphenator objects on assignment. But I don't
pretend that this is anything *but* runtime checking.

-austin
 
A

Austin Ziegler

Of course, respond_to? doesn't tell you about what the method does. But
it isn't meant to. To check an object's signature is not the same thing
as to check that it does what you want it to do. A file object that
responds to write() may output a file in the current working directory;
but a mock object which is _acting like_ a file object may just write
to screen, or may not write anything at all. But at least you can tell
that it _acts like_ a file object so far as its signature is concerned
(viz., duck typing).

Not without doing more than #respond_to? checking. See other responses
that I've made.

-austin
P.S. Please don't snip context when you're posting a response to
someone. Just because you might be using a threaded newsreader or
mailer doesn't mean that everyone is. A little context goes a long
way.
 
M

MonkeeSage

Austin said:
No, you better not. If I use #<<, then I can "write" to a String, a
StringIO, an IO (socket, file, etc.), or an Array. Checking for #write
limits you to (mostly) actual IO objects.

Then you'd be asking if the object _acts like_ (quacks like) an
enumerable, would you not? Why wrap that in a begin....rescue clause
and only spot it after the fact when you can catch it ahead of time?
Certainly it doesn't violate duck-typing to ask what an object acts
like (which is what imposing conditions on an object would be doing)?!

Regards,
Jordan
 
S

Simen Edvardsen

Then you'd be asking if the object _acts like_ (quacks like) an
enumerable, would you not? Why wrap that in a begin....rescue clause
and only spot it after the fact when you can catch it ahead of time?
Certainly it doesn't violate duck-typing to ask what an object acts
like (which is what imposing conditions on an object would be doing)?!

Because with duck typing, you break exactly at the point where an
object doesn't behave like it's supposed to, not before or (worse)
later.
 
A

Austin Ziegler

Then you'd be asking if the object _acts like_ (quacks like) an
enumerable, would you not? Why wrap that in a begin....rescue clause
and only spot it after the fact when you can catch it ahead of time?
Certainly it doesn't violate duck-typing to ask what an object acts
like (which is what imposing conditions on an object would be doing)?!

As Simen Edvardsen says, preconditions or exception handling can
change the location of the error reported so that you're not sure
whether you're dealing with the error at the right location or not.
There are good reasons for having both, but they're *additional*
techniques that can be used.

But I didn't say anything about wrapping #<< calls in a
begin/rescue/end clause. I didn't ask whether the object acts like an
enumerable (because, in fact, I don't care if it's enumerable or not).
I just told it #<< with some arguments. If it doesn't actually
understand #<<, it's going to break -- and that means someone gave me
an object that doesn't work like it is supposed to, or I transformed
the object in a negative way.

When one is duck typing, one is applying a concept, not formalizing an
interface. Ducktator is many things, but it isn't an "implementation
of duck-typing". By definition, there can be no implementation of a
duck type validator -- it's not something that is able to be
statically defined at any point.

-austin
 
J

Joel VanderWerf

Austin said:
This is demonstrably untrue. Duck typing is not about validation. It's
about trusting your callers to do the right thing -- and then doing
the right thing when they don't.

To extend Austin's point a little: In ruby, it really has to be this
way. What if an object responds to a message by way of method_missing?
There's no easy way to validate that.
 
L

Leslie Viljoen

To extend Austin's point a little: In ruby, it really has to be this
way. What if an object responds to a message by way of method_missing?
There's no easy way to validate that.

Of course the whole scheme really relies on the library writer to
provide very good documentation - they have to (re)discover and
document every method call they perform on every object passed to
them. Unless they fall back to the old ways and say "this method
expects a string, and all that that implies". Which doesn't help the
user of the library much because say they want to pass in something
else that works a little like a string, but does not exhaustively
support every method the String class supports? They have to read the
library source and discover and document every method call on the
object...
 
R

Robert Klemme

Leslie said:
Of course the whole scheme really relies on the library writer to
provide very good documentation - they have to (re)discover and
document every method call they perform on every object passed to
them. Unless they fall back to the old ways and say "this method
expects a string, and all that that implies". Which doesn't help the
user of the library much because say they want to pass in something
else that works a little like a string, but does not exhaustively
support every method the String class supports? They have to read the
library source and discover and document every method call on the
object...

Maybe it has to be a String - maybe not. If not, you can use a probe

class Probe
def method_missing(*a,&b)
p [self, *a]
end
end

Kind regards

robert
 
O

Ola Bini

Well,

I don't want to continue this discussion in this venue, since the
discussion isn't really about what I meant with the project and such.
Duck typing also seems to be a very loaded (might I say religious) word
for people in the Ruby-community.

I've written a small blog post about the issue, but I can't guarantee
that we will all be friends, even after you read it:

http://ola-bini.blogspot.com/2006/09/dynamic-ruby-power-and-static-balance.html

Cheers
--
Ola Bini (http://ola-bini.blogspot.com)
JvYAML, RbYAML, JRuby and Jatha contributor
System Developer, Karolinska Institutet (http://www.ki.se)
OLogix Consulting (http://www.ologix.com)

"Yields falsehood when quined" yields falsehood when quined.
 
L

Leslie Viljoen

Leslie said:
Of course the whole scheme really relies on the library writer to
provide very good documentation - they have to (re)discover and
document every method call they perform on every object passed to
them. Unless they fall back to the old ways and say "this method
expects a string, and all that that implies". Which doesn't help the
user of the library much because say they want to pass in something
else that works a little like a string, but does not exhaustively
support every method the String class supports? They have to read the
library source and discover and document every method call on the
object...

Maybe it has to be a String - maybe not. If not, you can use a probe

class Probe
def method_missing(*a,&b)
p [self, *a]
end
end

Ah thank-you, that's great. But it would not work unless every
execution path within the method receiving the object was executed:

class Probe
def method_missing(*a,&b)
p [self, *a]
end
end

def testProbe(probe)
if probe>5
puts probe.destroy
else
puts probe.accept
end
end

p = Probe.new
testProbe(p)

---------------------
lesliev@derik:~$ ./testProbes.rb
[#<Probe:0xb7cbaa70>, :>, 5]
[#<Probe:0xb7cbaa70>, :accept]
nil



...so is there some magic that could overcome that limitation?
 
R

Robert Klemme

Leslie Viljoen said:
Ah thank-you, that's great. But it would not work unless every
execution path within the method receiving the object was executed:

Well, yes. I didn't say it's perfect. :)
..so is there some magic that could overcome that limitation?

You could make Probe significantly more complex so that it records invoked
methods and alters the return value between true and false or something else
etc. and goes on probing until no new execution sequences are found. I
guess this can soon get messy...

Regards

robert
 
L

Leslie Viljoen

Well, yes. I didn't say it's perfect. :)


You could make Probe significantly more complex so that it records invoked
methods and alters the return value between true and false or something else
etc. and goes on probing until no new execution sequences are found. I
guess this can soon get messy...

Yes, it starts out as a nice idea. And while it's doing all that
probing, who knows what side-effects it might be producing in that
alien code?!

Let me see, why not try:

format_harddrive_if_input_above_5(probe)

Whoops!

Anyway, there are still places where it helps, I'm not complaining ;-)

Les
 

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,992
Messages
2,570,220
Members
46,805
Latest member
ClydeHeld1

Latest Threads

Top