Type checking function parameters

N

Nick Green

More or less all my functions look something like

def foo bar baz quux
if not bar.is_a? String or
not baz.is_a? FunkyDinosaur or
not quux.respond_to? "getEatenByFunkyDinosaur"
#complain about errors w/ raise or app specific complain function
end
# do some stuff
end


There has got to be a better way to go about this parameter checking
business, but googling is not working (bad search terms maybe). Is
there?

And I don't really mean other ways of writing the same logic, i.e.

3 statements that look like:

complain "baz is no dinosaur!" unless baz.is_a? FunkyDinosaur

does not seem a whole lot better to me than the above example
 
E

Eleanor McHugh

More or less all my functions look something like

def foo bar baz quux
if not bar.is_a? String or
not baz.is_a? FunkyDinosaur or
not quux.respond_to? "getEatenByFunkyDinosaur"
#complain about errors w/ raise or app specific complain function
end
# do some stuff
end


There has got to be a better way to go about this parameter checking
business, but googling is not working (bad search terms maybe). Is
there?

And I don't really mean other ways of writing the same logic, i.e.

3 statements that look like:

complain "baz is no dinosaur!" unless baz.is_a? FunkyDinosaur

does not seem a whole lot better to me than the above example

You're looking at the problem inside-out which is why you're finding
yourself in such a tangle: in Ruby explicit type checking is very rare
and in cases where you think you need it you're generally wrong and
fighting against the language rather than flowing with it. Google the
term "Duck Typing" and you should find lots of helpful advice on how
to write your code in an idiomatic manner whilst still covering your
arse against the kind of type errors which can occur using Exception
handling.


Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-brains.net
 
P

Paul Smith

My first stab at some Ruby started like this too.

Let it go.

Stop caring about what type your parameters are, and just let them
complain if you try to call a method which they don't respond to.

It's unsatisfying at first, if you've been brainwashed into writing
code where you check everything to try to prevent illegal instructions
from happening, but you'll soon realise that it actually gives you a
lot of freedom and power.

If it walks like a duck and quacks like a duck then it's probably a duck.

You're looking at the problem inside-out which is why you're finding
yourself in such a tangle: in Ruby explicit type checking is very rare an= d
in cases where you think you need it you're generally wrong and fighting
against the language rather than flowing with it. Google the term "Duck
Typing" and you should find lots of helpful advice on how to write your c= ode
in an idiomatic manner whilst still covering your arse against the kind o= f
type errors which can occur using Exception handling.


Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-brains.net



--=20
Paul Smith
http://www.nomadicfun.co.uk

(e-mail address removed)
 
N

Nick Green

OK...

I see the idea, and it is tempting. For code written for me to be used
by me it is in fact ideal. However, I guess I have two follow up
questions, both about failing gracefully:

What is the "right" way to make sure the errors these functions report
are nice to people who may use the functions/libraries I code? "Foo.bar
takes a Duck, not a Cow" would be a very helpful error message. object
of type Cow does not have method of type quack, is also not a bad error
message, but it would be nice to let the user know which type of object
in fact does have the method "quack" (presumably it would not always be
as obvious as "quack"==>"duck"). Or is the ruby way, since its an
interpereted language, for a user of my library to crack open the ruby
or rdoc/ri and look?

What is the "right" way to make sure that, if my libraries or functions
are misused by someone (possibly me :p), the user of the script/program
receives a non-programmer-targeted friendly error message like "Whoops,
we sent a Cow to do a Ducks job. Feel free to report the bug to
(e-mail address removed). Make sure to include the
following: <normal error dump>"?
 
E

Eleanor McHugh

I see the idea, and it is tempting. For code written for me to be
used
by me it is in fact ideal. However, I guess I have two follow up
questions, both about failing gracefully:

What is the "right" way to make sure the errors these functions report
are nice to people who may use the functions/libraries I code?
"Foo.bar
takes a Duck, not a Cow" would be a very helpful error message.
object
of type Cow does not have method of type quack, is also not a bad
error
message, but it would be nice to let the user know which type of
object
in fact does have the method "quack" (presumably it would not always
be
as obvious as "quack"==>"duck"). Or is the ruby way, since its an
interpereted language, for a user of my library to crack open the ruby
or rdoc/ri and look?

The Ruby way isn't to try and defend against behaviour which is out of
your control. By publishing an API for your library you provide a
contract and it is the responsibility of the programmer using your
library to respect and conform to that contract. Whilst this can seem
quite daunting if you come from a language such as Java, you'll find
that it soon becomes a great liberator - not least because all objects
that respond to a particular message can be used interchangeably.

Likewise exception handling allows you to define clean error recovery
based on scope.
What is the "right" way to make sure that, if my libraries or
functions
are misused by someone (possibly me :p), the user of the script/
program
receives a non-programmer-targeted friendly error message like
"Whoops,
we sent a Cow to do a Ducks job. Feel free to report the bug to
(e-mail address removed). Make sure to include the
following: <normal error dump>"?


In the general case there is no way to know the full set of objects
which can respond to a given message at a given time: a duck may
quack, but so for that matter might a duck hunter or a tape recorder.
This holds in Ruby because the runtime behaviour of objects is mutable
- methods might be added and removed at any time during program
execution. This is why it's much more useful to rescue runtime
exceptions and handle them elegantly than to worry about the class of
an object.

So just relax and let duck typing happen, you'll find those tight
controls your brain's learned with other languages really are
irrelevant in Ruby.


Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-brains.net
 
J

James Edward Gray II

The Ruby way isn't to try and defend against behaviour which is out
of your control. By publishing an API for your library you provide a
contract and it is the responsibility of the programmer using your
library to respect and conform to that contract. Whilst this can
seem quite daunting if you come from a language such as Java, you'll
find that it soon becomes a great liberator - not least because all
objects that respond to a particular message can be used
interchangeably.

Likewise exception handling allows you to define clean error
recovery based on scope.



In the general case there is no way to know the full set of objects
which can respond to a given message at a given time: a duck may
quack, but so for that matter might a duck hunter or a tape
recorder. This holds in Ruby because the runtime behaviour of
objects is mutable - methods might be added and removed at any time
during program execution. This is why it's much more useful to
rescue runtime exceptions and handle them elegantly than to worry
about the class of an object.

So just relax and let duck typing happen, you'll find those tight
controls your brain's learned with other languages really are
irrelevant in Ruby.

That was so beautiful I almost teared up. ;)

James Edward Gray II
 
S

spiralofhope

Along the lines of this thread..

I'm rough around the edges, so I'll just use some general language.

I have a habit of building my code like bunches of little black boxes.
Each box accepts a certain kind of input and returns a certain kind
of output.

For each of the boxes, I create a sort of gateway that ensures that a
box only receives its expected input, and it intelligently reports an
error if that's not the case.

Should I be loosening my grip and removing some or perhaps all of
those gateways, just like the explanations for duck typing?
 
J

Josh Cheek

[Note: parts of this message were removed to make it a legal post.]

Along the lines of this thread..

I'm rough around the edges, so I'll just use some general language.

I have a habit of building my code like bunches of little black boxes.
Each box accepts a certain kind of input and returns a certain kind
of output.

For each of the boxes, I create a sort of gateway that ensures that a
box only receives its expected input, and it intelligently reports an
error if that's not the case.

Should I be loosening my grip and removing some or perhaps all of
those gateways, just like the explanations for duck typing?
I would, if you provide the correct input, the gateway is not necessary (and
it provides additional steps, hence adding some small increase in execution
time). If you do not provide the correct input, it can be expected to break,
or at least work incorrectly, which should indicate that you need to check
how you are calling it. Creating a gateway that breaks it intentionally
seems redundant, and it creates more places you have to go look if you want
to change something, more code you need to manage, etc. Instead, I would
just name my variables something intuitive, and explain what variables the
code expects, in a comment.
 
D

David A. Black

Hi --

Along the lines of this thread..

I'm rough around the edges, so I'll just use some general language.

I have a habit of building my code like bunches of little black boxes.
Each box accepts a certain kind of input and returns a certain kind
of output.

For each of the boxes, I create a sort of gateway that ensures that a
box only receives its expected input, and it intelligently reports an
error if that's not the case.

Should I be loosening my grip and removing some or perhaps all of
those gateways, just like the explanations for duck typing?

Probably. For one thing, in Ruby, it's very, very difficult to verify
whether or not an argument is going to satify the needs of the method.
You can check whether something is of class String, for example, but
then you need to wonder why a StringIO object, or another object whose
type can handle what you want it to do, won't also work.

By "type", I do not mean "class". In Ruby, type and class are not the
same. Every object is an instance of a class, but the type of an
object is the sum of what the object is capable of at a given time.
That's a very elastic and kind of inductive thing, which is why the
notion of type is actually kind of moot in Ruby.

Of course you can get a certain amount of mileage by testing the class
of an object and assuming that if it's of a certain class, it must
behave a certain way. But by doing so, you create two problems:

1. it doesn't really work (because class is not the same as type);
2. you stop yourself from creating more flexible, agile (small 'a'
:) program design.


David

--
David A. Black / Ruby Power and Light, LLC / http://www.rubypal.com
Ruby/Rails training, mentoring, consulting, code-review
Latest book: The Well-Grounded Rubyist (http://www.manning.com/black2)

September Ruby training in NJ has been POSTPONED. Details to follow.
 
E

Eleanor McHugh

Of course you can get a certain amount of mileage by testing the class
of an object and assuming that if it's of a certain class, it must
behave a certain way. But by doing so, you create two problems:

1. it doesn't really work (because class is not the same as type);

Indeed the fact that both classes and objects are open and can have
methods added, removed or replaced at any point during runtime
execution means that even if a method checks to ensure that one of its
parameters is for example a String object, that object may well not
have the full complement of methods defined by the String class at the
time code seeks to manipulate it and hence could still raise a
MethodMissing exception.

Locking down object identity is therefore a cumbersome approach to
type safety that sacrifices much of the flexibility that makes Ruby a
pleasure to work with, but in those rare cases where it might seem
desirable good practice is to filter based upon responds_to not about
Class identity. In general though code will be more robust if the
emphasis is placed on good exception handling and testing rather than
type checking.


Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-brains.net
 
N

Nick Green

Lots of helpful opinions/philosophies about ruby here.

As for the respond_to? is more useful than is_a? comment, I typecheck
almost everything with respond to (ok thats not really a "type"check...
i... check things with respond_to?)

However, heres one issue I have, its not complicated, but its simpler to
say in code than words:

def foo someint
someint+7
end

if foo "hello"
puts "hi"
else
puts "oh noez!"
end

Putting this in irb only gives me an error. There are times I would
like to say, if this function works, do this, otherwise, do this. And
its not neccesarily unacceptable for the function to fail, but I wan't
to know if it failed so I can do something about that. I realize that
many (most?) functions you just figure its bad news if it fails, but I
have enough functions that I just redo/try something else/figure out
whats wrong if the function fails that I need a way to say this. In
less fun/happy languages this looks like


ret = putButterOn(my_pancake);
if (ret != BUTTER_APPLIED)
{
buttering_queue.enqueue(my_waffle);
return ERROR_PANCAKE_NOT_BUTTERED;
}

And here, if I don't really have my heart set on a buttered pancake,
but I need to know if this putButterOn function was not build to handle
pancakes (perhaps pancake.respond_to? "spreadButter" == false). I
realize this code is not really a likely way to handle things, but it
gets the idea across. If I don't manually check values and return
nil/false/an error code if things go wrong, how can I keep going if a
function call fails? This works for checking errors in irb, but an
actual script appears to give up when it errors...

ret = putButterOn my_pancake
if ret == nil
butter_queue.push my_waffle
end
 
J

Josh Cheek

[Note: parts of this message were removed to make it a legal post.]

Lots of helpful opinions/philosophies about ruby here.

As for the respond_to? is more useful than is_a? comment, I typecheck
almost everything with respond to (ok thats not really a "type"check...
i... check things with respond_to?)

However, heres one issue I have, its not complicated, but its simpler to
say in code than words:

def foo someint
someint+7
end

if foo "hello"
puts "hi"
else
puts "oh noez!"
end

Putting this in irb only gives me an error. There are times I would
like to say, if this function works, do this, otherwise, do this. And
its not neccesarily unacceptable for the function to fail, but I wan't
to know if it failed so I can do something about that. I realize that
many (most?) functions you just figure its bad news if it fails, but I
have enough functions that I just redo/try something else/figure out
whats wrong if the function fails that I need a way to say this. In
less fun/happy languages this looks like


ret = putButterOn(my_pancake);
if (ret != BUTTER_APPLIED)
{
buttering_queue.enqueue(my_waffle);
return ERROR_PANCAKE_NOT_BUTTERED;
}

And here, if I don't really have my heart set on a buttered pancake,
but I need to know if this putButterOn function was not build to handle
pancakes (perhaps pancake.respond_to? "spreadButter" == false). I
realize this code is not really a likely way to handle things, but it
gets the idea across. If I don't manually check values and return
nil/false/an error code if things go wrong, how can I keep going if a
function call fails? This works for checking errors in irb, but an
actual script appears to give up when it errors...

ret = putButterOn my_pancake
if ret == nil
butter_queue.push my_waffle
end
def foo someint
someint+7
end

begin
foo "hello"
puts "hi"
rescue
puts "oh noez!"
end


Personally, I've been wondering what would happen if nil just returned nil
for every undefined method. Essentially, instead of always having to put
if x && x.value && x.value > 3

I could just put
if x.value > 3

because if x was nil, then nil.value would be nil, and nil.>(3) would be
nil.

I just wonder if this would break things, or is a naive question to ask, so
I've been a little silent about it. But it keeps coming back to me, because
a lot of edge case errors revolve around nil not responding to methods.
 
B

Bill Kelly

From: "Josh Cheek said:
Personally, I've been wondering what would happen if nil just returned nil
for every undefined method. Essentially, instead of always having to put
if x && x.value && x.value > 3

I could just put
if x.value > 3

because if x was nil, then nil.value would be nil, and nil.>(3) would be
nil.

I just wonder if this would break things, or is a naive question to ask, so
I've been a little silent about it. But it keeps coming back to me, because
a lot of edge case errors revolve around nil not responding to methods.

Can if you want:

irb(main):022:0> class NilClass
irb(main):023:1> def method_missing(*args)
irb(main):024:2> nil
irb(main):025:2> end
irb(main):026:1> end

irb(main):027:0> nil.foo.bar
=> nil
irb(main):028:0> nil > 3
=> nil

etc.

Objective-C works that way.

The downside is, for cases where you are calling a method
on an object you really did not want or expect to be nil,
you no longer get the error at the point of that method
call. To me having code fail fast in error conditions is
more important, so I don't patch NilClass as shown above.
But, ruby allows it if you really want.


Regards,

Bill
 
D

David A. Black

Hi --

Can if you want:

irb(main):022:0> class NilClass
irb(main):023:1> def method_missing(*args)
irb(main):024:2> nil
irb(main):025:2> end
irb(main):026:1> end

irb(main):027:0> nil.foo.bar
=> nil
irb(main):028:0> nil > 3
=> nil

etc.

Objective-C works that way.

The downside is, for cases where you are calling a method
on an object you really did not want or expect to be nil,
you no longer get the error at the point of that method
call. To me having code fail fast in error conditions is
more important, so I don't patch NilClass as shown above.
But, ruby allows it if you really want.

I wouldn't do it, though, even if I really wanted it :) There may be
code in the standard library, or elsewhere, that's relying on the
documented behavior of nil.


David

--
David A. Black / Ruby Power and Light, LLC / http://www.rubypal.com
Ruby/Rails training, mentoring, consulting, code-review
Latest book: The Well-Grounded Rubyist (http://www.manning.com/black2)

September Ruby training in NJ has been POSTPONED. Details to follow.
 
D

David A. Black

Hi --

Lots of helpful opinions/philosophies about ruby here.

As for the respond_to? is more useful than is_a? comment, I typecheck
almost everything with respond to (ok thats not really a "type"check...
i... check things with respond_to?)

Actually respond_to? is the closest thing to a built-in tool that Ruby
offers for type-checking. is_a? just checks class and class/module
ancestry.
However, heres one issue I have, its not complicated, but its simpler to
say in code than words:

def foo someint
someint+7
end

if foo "hello"
puts "hi"
else
puts "oh noez!"
end

Putting this in irb only gives me an error. There are times I would
like to say, if this function works, do this, otherwise, do this. And
its not neccesarily unacceptable for the function to fail, but I wan't
to know if it failed so I can do something about that. I realize that
many (most?) functions you just figure its bad news if it fails, but I
have enough functions that I just redo/try something else/figure out
whats wrong if the function fails that I need a way to say this.

You'll get an exception there which you can intercept and handle. If
you're really cascading through various methods until you find one
that doesn't blow up, that's about the only way to do it. It sounds a
bit fragile, though.


David

--
David A. Black / Ruby Power and Light, LLC / http://www.rubypal.com
Ruby/Rails training, mentoring, consulting, code-review
Latest book: The Well-Grounded Rubyist (http://www.manning.com/black2)

September Ruby training in NJ has been POSTPONED. Details to follow.
 
N

Nick Green

Yes... I pretty much fail at life for not seeing how I should be
handling this:

begin
rescue
end


Right on. Understood. Those go around the call TO my library IN the
calling function.

def butterUp butter_queue
return if butter_queue.empty?
begin
putButterOn butter_queue.shift
rescue err
logMsg err
butterUp
end
end

Would be a good way to do it ya? Obviously this is some kind of
ruby/pseudo code hybrid, but the idea is there. Heheh I feel silly, I
forgot where one should deal with errors. Too many years of C had my
obsessed with return values and thousands of lines of error checking all
over the place.
 
E

Eleanor McHugh

def butterUp butter_queue
return if butter_queue.empty?
begin
putButterOn butter_queue.shift
rescue err
logMsg err
butterUp
end
end


A couple of suggestions.

Firstly, camelCase looks really ugly in Ruby programs so I'm going to
inject the underscores in the following comments.

It would make more sense to populate the butter_queue with objects
that respond to put_butter_on as these appear to be the message
recipients. This would also mean that instead of checking on each
access for an empty queue you could either rescue or propagate the
NoMethodError when butter_queue.shift produces a nil value.

Taking this idea a step further, butter_up is clearly a method of a
ButterQueue object, so the whole might be better expressed as:

class ButterQueue
def initialize
@Queue = []
end

def butter_up
begin
@queue.shift.but_butter_on
rescue Exception=> e
log_message e
raise
end
end
end

I've rewritten the error handling so that it creates a message in the
log (assuming log_message to be defined somewhere in scope) and then
re-raised it so that the calling code can decide how to handle it.
This allows an error to be handled separately from a nil value return
as the latter may have some specific semantic meaning.

I hope this all makes sense, but if not I'd be happy to discuss it
further off-list.


Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-brains.net
 
N

Nick Green

Hahah, yes I pulled the example completely out of my... well out of thin
air;p I wasn't thinking how one would implement the whole, just trying
to get a feel for error handling. However, your in depth critique of my
culinary function is appreciated, as is your rescue example. I
understand that were it in a bigger program the failsafes would be
elsewhere, but I do think there are cases where, so to speak, "the show
must go on" and I was wondering how best to go about that in ruby. This
thread has more-than-answered my question :)

Also, great slides on the link in your sig.

p.s. I like camel case :'( and i do indeed define logMessage in most of
my code;P

Eleanor said:
def butterUp butter_queue
return if butter_queue.empty?
begin
putButterOn butter_queue.shift
rescue err
logMsg err
butterUp
end
end


A couple of suggestions.

Firstly, camelCase looks really ugly in Ruby programs so I'm going to
inject the underscores in the following comments.

It would make more sense to populate the butter_queue with objects
that respond to put_butter_on as these appear to be the message
recipients. This would also mean that instead of checking on each
access for an empty queue you could either rescue or propagate the
NoMethodError when butter_queue.shift produces a nil value.

Taking this idea a step further, butter_up is clearly a method of a
ButterQueue object, so the whole might be better expressed as:

class ButterQueue
def initialize
@Queue = []
end

def butter_up
begin
@queue.shift.but_butter_on
rescue Exception=> e
log_message e
raise
end
end
end

I've rewritten the error handling so that it creates a message in the
log (assuming log_message to be defined somewhere in scope) and then
re-raised it so that the calling code can decide how to handle it.
This allows an error to be handled separately from a nil value return
as the latter may have some specific semantic meaning.

I hope this all makes sense, but if not I'd be happy to discuss it
further off-list.


Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-brains.net
 
R

Robert Klemme

2009/9/10 Eleanor McHugh said:
class ButterQueue
=A0def initialize
=A0 =A0@queue =3D []
=A0end

=A0def butter_up
=A0 =A0begin
=A0 =A0 [email protected]_butter_on
=A0 =A0rescue Exception=3D> e
=A0 =A0 =A0log_message e
=A0 =A0 =A0raise
=A0 =A0end
=A0end
end

I've rewritten the error handling so that it creates a message in the log
(assuming log_message to be defined somewhere in scope) and then re-raise= d
it so that the calling code can decide how to handle it. This allows an
error to be handled separately from a nil value return as the latter may
have some specific semantic meaning.

I find it debatable whether this pattern does make sense: if the
calling code can handle the exception in a meaningful way, there is no
point in logging it, because that would attract suspicion where it is
not in order. I would say that logging is a form of handling, i.e. if
the calling code does not know what to do about it, it can still log
the error. If you log it in the source location then this could be
viewed as violating the principle of "let the calling code decide how
to handle the exception".

One more reason against this pattern: if this is done on multiple
levels you'll get the exact same error multiple times in the log which
does not really help clear things up.
I hope this all makes sense, but if not I'd be happy to discuss it furthe= r
off-list.

Please let's keep it on list. We would miss your mellow home brew comments=
:)

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 

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,997
Messages
2,570,239
Members
46,827
Latest member
DMUK_Beginner

Latest Threads

Top