RCR 303: nil should accept missing methods and return nil

A

Ara.T.Howard

Hi,

In message "Re: RCR 303: nil should accept missing methods and return nil"

|Be careful about exactly what is being asked for and what has been done
|before. I once developed an OOP system were all objects would respond to
|all undefined messages by doing nothing. I rapidly changed that to an
|error condition as in that case the complaint that it hid bugs was valid.

I know Objective-C's nil works like that. I once developed an OOP
system (which was an early version of Ruby) where nil would respond to
all undefined messages by doing nothing. In production code, it does
nothing bad, since any production code should not raise an exception.
Rather it introduces new scheme of error handling.

But during development, it can hide bugs. It is very difficult to
distinguish correct error handling by ignoring unknown message, and
uncontrolled unintentional misbehavior caused by bugs. It's against
the principle of "early death".

Your proposal worth something, I think. But it's not going to be seen
in the near future of Ruby.

perhaps

nil.blackhole = true

to change nil's behaviour. that way it could be changed in production code?

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| renunciation is not getting rid of the things of this world, but accepting
| that they pass away. --aitken roshi
===============================================================================
 
L

Lionel Thiry

Jim Weirich a écrit :
Actually, it is the current behavior that forces you to write the "unless
foo.nil?", since sending the "bar" message to nil is an error. If nil was
changed to be a message eating variant, then

foo.bar
and
foo.bar unless foo.nil?

would be equivalent. In other words, the change would help alleviate your RSI
problems.

But, if foo wasn't supposed to be nil, what would happen? foo.bar gives nil,
which would be used by other operations that each would give nil, nil, and nil
again. And at the end of the chain, you don't have your expected behavior,
because there was a bug. From where did the first unwanted nil come from? Where
is that bug?

Then, to prevent such annoying bug to happen, I suppose I'd feel forced to write
this each time I suspect some foo could be nil:
if $debug && foo.nil?
raise "foo is nil"
else
foo.bar
end

My opinion is that: nil handling is annoying but difficult debuging is worse.
I will admit that my gut reaction is to fall in the "should throw an
exception" camp. But the idea of a universal Null Design Pattern with nil is
intriguing. John's suggested experiment is fairly painless ... I'm willing
to try it and see if I'm persuaded.

If the initial goal of this RCR is to make the Null Design Pattern somewhat
universal in ruby, what about having a new litteral like for example
"null_object" that would do that job?
 
A

Ara.T.Howard

Well, there is already $DEBUG. I think that should work fine, in case
this proposal were accepted.

not at all - $DEBUG would need to be on by defualt then and most of the time
then and this causes reams of output on STDERR for many libs in addition to
subtly changing their behaviour.

what i'm suggesting is a 'normal' behaviour whereby nil throws NoMethodError
(current default) - note that this is NOT $DEBUG=true - and a built-in swith
to turn it off

nil.blackhole = true

or perhaps a new global

$NIL_BLACKHOLE = true

or maybe better

NilClass::blackhole = true

to cause nil to obey the null pattern.

this feature wouldn't affect any code now since the normal behaviour would
remain the same. $DEBUG is a global that affects tons of code in addition to
the (proposed) nil mods and therefore can't be used i think.

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| renunciation is not getting rid of the things of this world, but accepting
| that they pass away. --aitken roshi
===============================================================================
 
B

Brian Schröder

not at all - $DEBUG would need to be on by defualt then and most of the time
then and this causes reams of output on STDERR for many libs in addition to
subtly changing their behaviour.

what i'm suggesting is a 'normal' behaviour whereby nil throws NoMethodError
(current default) - note that this is NOT $DEBUG=true - and a built-in swith
to turn it off

nil.blackhole = true

or perhaps a new global

$NIL_BLACKHOLE = true

or maybe better

NilClass::blackhole = true

to cause nil to obey the null pattern.

this feature wouldn't affect any code now since the normal behaviour would
remain the same. $DEBUG is a global that affects tons of code in addition to
the (proposed) nil mods and therefore can't be used i think.

cheers.

Wouldn't it be even simpler to include the proposed nillify.rb in the
standard and use either
require 'nillify' or
ruby -rnillify.

Or even simpler, just include the declaration in the program?

regards,

Brian
 
A

Austin Ziegler

If there were a lot of programs deliberately doing

begin
# some complex calculation
rescue NoMethodError => details
# Do some valid functionality, not just error handling
end

I would agree with out.
If they are not doing that, then the program would just crash
totally. My change would change the behaviour from crashing, to
Doing The Right thing.

Um. I think that the point being made is that crashing, in this
case, *is* doing the right thing. If nil respondedd to all methods
silently, how would you ever know when your method got the *wrong*
data?

I said this on the RCR comment area, but PDF::Writer is *correct* to
blow up when it gets nil. Without this, there are things that I
could not *possibly* have debugged. With the silent eating of
messages, the generated PDF files would be incorrect -- invalid --
and there's not a damned thing I could do to tell where the problem
is.

I don't really know what to say to this -- this will turn a correct
behaviour into a completely invalid behaviour.

-austin
 
A

Austin Ziegler

I would suggest next 5 times you have a nil class throws
NoMethodError crash, just add this at the head of your program...

No. I can tell you from experience over the last four days that I
have fixed no fewer than five *real* bugs in PDF::Writer that would
have resulted in the incorrect generation of PDF output had I done
this. Probably even created a PDF that would crash any PDF reader
out there.
If, your program then works correctly, then I am right.

This will happen in only a few applications; I would argue it will
never happen in anything that has to communicate with the outside
world.
If, your still program thows an exception or reports an error, but
perhaps a slightly different point, then I am still right, namely
this change doesn't hide bugs.

Yes, it does, at least with PDF::Writer. The output for PDF has to
be correct. And in this case, PDF::Writer would simply do the wrong
thing.

-austin
 
A

Austin Ziegler

Actually, it is the current behavior that forces you to write the
"unless foo.nil?", since sending the "bar" message to nil is an
error.

Not really. The precise example I gave is a bad one. But generally,
if I'm calling #do_something, it's because I want something done. If
I have a nil object instead, then I have a problem -- and I want to
know it as early as possible.

I've emphasized this in both fora, but there have been some problems
during the redevelopment of PDF::Writer that *would* have been
masked by this sort of behaviour, resulting in bad output. If I had
this, then I would have to comb through the bad output to find the
problem. This is difficult when the fault might be literally
hundreds (or thousands) of lines away from what generates the error.

If I say #do_something and have a nil object, this proposal merely
masks the problem -- that I got a nil object to operate on. If what
I'm using is never supposed to return nil, but did anyway, this is
definitely wrong.

(And yes, I have had *exactly this problem*. No, I can't show you
the code -- I fixed the problem and the code doesn't exist anywhere
at this point.)

For a very small class of problems, this will work. For anything
that has to interact outside of the Ruby program in question, this
is -- and always will be -- a disaster.

-austin
 
P

Patrick Hurley

I would suggest next 5 times you have a nil class throws
NoMethodError crash, just add this at the head of your program...

class NilClass
def method_missing( sym, *args)
nil
end
end

And see what happens.

If, your program then works correctly, then I am right.

If, your still program thows an exception or reports an error, but perhaps
a slightly different point, then I am still right, namely this change
doesn't hide bugs.

If the program silent proceeds to do the wrong thing, then I am wrong.


A simple example of "wrong" behavior (using the nil returns of !
methods which is another can of worms :)

class NilClass
def method_missing(sym, *args)
nil
end
end

x = "Pat"
x.strip!.upcase!

#use x in some important way....
# this now fails silently, the expectation
# was for an upcased string with no "extra" white space....
puts x
 
J

Jim Weirich

Austin Ziegler said:
For a very small class of problems, this will work. For anything
that has to interact outside of the Ruby program in question, this
is -- and always will be -- a disaster.

As I mentioned before, I'm in the "throw an exception" camp. But the
argument that it will be a "disaster" sounds like the type of arguement a
static typer levels againts dynamic languages. So, I'm open to an
experiment.

And the first opportunty came up today. I am revising some command line
scripts that access a database. You have to specify an environment so
that the proper database case be choson. I forgot to specify the
environment and got:

$ ruby -Ilib bin/info.rb r887
lib/ics/folderid.rb:83:in `translate_rids': undefined method `sql' for
nil (NameError)

Then I added the -rnilify argument...

$ ruby -rnilify -Ilib bin/info.rb r887
Can't find folder for ID r887

So, adding in a message eating nil causes the script to think it can't
find the appropriate folder. While true, it is certainly not the best
error message that could be produced. I would imagine that tracing down
the source of this error would be more difficult than the other message. I
knew exactly what the problem was when I saw the NameError. The "can't
find" error leaves plenty of room for other possibilities.

So, at first blush, I'm still in the "throw an exception" camp.

On a side note, this whole discussion inspired me to solve the problem in
an interesting way. I just make sure I initialize the database object
with an instance of the following class:

class MissingDatabase
def method_missing(sym, *args, &block)
fail "No Database Specified"
end
end
 
J

Joel VanderWerf

Jim said:
On a side note, this whole discussion inspired me to solve the problem in
an interesting way. I just make sure I initialize the database object
with an instance of the following class:

class MissingDatabase
def method_missing(sym, *args, &block)
fail "No Database Specified"
end
end

Very nice! Some elaboration:

$ cat missing_obj.rb
class MissingObject < StandardError
def initialize(comment="")
@comment = comment
@location = caller[0][/.*?:\d+/]
end
def method_missing(meth, *args, &block)
raise self, "#{@location}: #{@comment}"
end
end

class MissingDatabase < MissingObject; end

def get_db_from_user_input
end

# Using nil:
my_db = get_db_from_user_input()
begin
my_db.lookup("foo")
rescue => e
p e
end

# Using a MissingObject
my_db = get_db_from_user_input() ||
MissingDatabase.new("No user input")
begin
my_db.lookup("foo")
rescue => e
p e
end

$ ruby missing_obj.rb
#<NoMethodError: undefined method `lookup' for nil:NilClass>
#<MissingDatabase: missing_obj.rb:26: No user input>
 
S

Saynatkari

Le 6/5/2005 said:
Austin Ziegler said:

As I mentioned before, I'm in the "throw an exception" camp. But the
argument that it will be a "disaster" sounds like the type of arguement a
static typer levels againts dynamic languages. So, I'm open to an
experiment.

And the first opportunty came up today. I am revising some command line
scripts that access a database. You have to specify an environment so
that the proper database case be choson. I forgot to specify the
environment and got:

$ ruby -Ilib bin/info.rb r887
lib/ics/folderid.rb:83:in `translate_rids': undefined method `sql' for
nil (NameError)

Then I added the -rnilify argument...

$ ruby -rnilify -Ilib bin/info.rb r887
Can't find folder for ID r887

So, adding in a message eating nil causes the script to think it can't
find the appropriate folder. While true, it is certainly not the best
error message that could be produced. I would imagine that tracing down
the source of this error would be more difficult than the other message. I
knew exactly what the problem was when I saw the NameError. The "can't
find" error leaves plenty of room for other possibilities.

So, at first blush, I'm still in the "throw an exception" camp.

Actually, throwing an exception might be better to do at an earlier
point. Essentially two types of nils exists; the expected ones and
the unexpected ones, of which the latter cause the problems here.
Most of these problems could be 'avoided', however, if instead
of returning nil, an exception were thrown.
On a side note, this whole discussion inspired me to solve the problem in
an interesting way. I just make sure I initialize the database object
with an instance of the following class:

class MissingDatabase
def method_missing(sym, *args, &block)
fail "No Database Specified"
end
end

E
 
F

Florian Frank

John said:
A very simple and generic way of improving the reliability of Ruby
programs is to implement the NullObject pattern by allowing nil to
accept all and every method instead of throwing a NoMethodError.

Great! I've so long waited for something like this. I have just written
a very reliable program, with this new feature:

class NilClass
def method_missing(*ignore) nil end
end

class Counter
def initialize
@count = 1
end

attr_reader :count

def increase
@count += 1
end
end

class DoSomething
def initialize
@coutner = Counter.new
end

def count_done
@counter.count
end

def do_it
@counter.increase
end
end

ds = DoSomething.new
10.times do
ds.do_it
end
puts "Have done it #{ds.count_done} times."
 
C

Cyent

On 5/5/05, John Carter <[email protected]> wrote:
I said this on the RCR comment area, but PDF::Writer is *correct* to
blow up when it gets nil. Without this, there are things that I
could not *possibly* have debugged. With the silent eating of
messages, the generated PDF files would be incorrect -- invalid --
and there's not a damned thing I could do to tell where the problem
is.

I don't really know what to say to this -- this will turn a correct
behaviour into a completely invalid behaviour.

Ah, but _would_ it. On any other object I would agree with you. But nil
occupies a special place in the ruby world.

Also, Ruby is a dynamic language, with objects all the way down.

nil means in effect, no thing is here.

What happens if I tell no thing to do something.

No thing happens and I'm left with no thing.

So I ask you, next time you are working on your code, when you hit and
undefine method on nil, NoMethodError. Do my little trick.

Put...

class NilClass
def method_missing(sym,*args)
nil
end
end

in a file nillit.rb, and pull that file into your code with
ruby -w -rnillit your_code

See what happens.

Is your assertion correct?

Do you actually generate incorrect PDF? (In which case you are right)

Does it just plain work? (In which case I am right)

Or does it blow up with an exception or error report somewhen else? (I
which case I'm still right.)

Whatever happens, try it, and what ever the answer, email me or add a
comment to the RCR and I will summarize to the list later.

I'm better a beer (to be collected in Christchurch New Zealand) that
practical software will find that...
a) Mostly it will just do the right thing.
b) Otherwise it will do the right thing for now, and be caught later as an
Exception or error report. (I suspect by adding a few more things like
class NilClass
def respond_to?( sym)
true
end
end
may cure some of those...
c) And very rarely, far more rarely than people suspect, will it silently
do the wrong thing.

Also as you go along, consider cases where you could just drop
conditionals. How many if branches would you be quietly dropping?

As for debugging, if you look at the RCR, the implementation I give
creates state on nil that records which method was missing and in which
calling context.

--
John Carter

The Cybernetic Entomologist - (e-mail address removed)

http://geocities.yahoo.com/cy_ent

I'm becoming less and less convinced of humans as rational beings.
I suspect we are merely meme collectors, and the reason meme is only
kept on to help count our change.
 
J

Joel VanderWerf

Cyent said:
Ah, but _would_ it. On any other object I would agree with you. But nil
occupies a special place in the ruby world.

Also, Ruby is a dynamic language, with objects all the way down.

nil means in effect, no thing is here.

What happens if I tell no thing to do something.

No thing happens and I'm left with no thing.

Always look on the bright side of life...
(I mean - what have you got to lose?)
(You know, you come from nothing - you're going back to nothing.
What have you lost? Nothing!)
Always look on the right side of life...

Now maybe that's an argument for Python to adopt this RCR!

(Apologies to Eric Idle.)
 
C

Cyent

Great! I've so long waited for something like this. I have just written
a very reliable program, with this new feature:

I presume the typo on @coutner was deliberate for illustrative purposes...

Unfortunately rubies run time switches are bug compatible with perl's.

If you read "man perl" and scroll down to the BUGS sections it says...

BUGS
The -w switch is not mandatory.

Anybody who is vaguely worried about bugs in their code whether they are
running perl or ruby should _always_ use the -w switch.

ruby -w frank.rb
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:29: warning: instance variable @counter not initialized
frank.rb:25: warning: instance variable @counter not initialized
Have done it times.


--
John Carter

The Cybernetic Entomologist - (e-mail address removed)

http://geocities.yahoo.com/cy_ent

I'm becoming less and less convinced of humans as rational beings.
I suspect we are merely meme collectors, and the reason meme is only
kept on to help count our change.
 
C

Cyent

I'm sorry, is this some kind of hoax? A program will not magically be
correct simply because an error is hidden. Think of all the subtle bugs
this would introduce, for example: you create an array with a fixed number
of elements and forget to give the default value 55. Now math would work,
but the results would be wrong - and you might not even catch this.

Think of an array with 42 elements. Access element 55. It returns nil. How
is this different?

If you think through the default values for arithmetic operations on nil
properly. My guess is the results in most case will be right.(How often do
you initialize an array to 55? 99.9% of the time you expect it to be full
of 0 or maybe 1.

How about nil autoconverts to the identity element for all operations. ie.
0 for +, 1 for *, "" for string concat.

In which case more code simplification, more code that just works.

The article at
http://www.smalltalkchronicles.net/edition2-1/null_object_pattern.htm that
you refer to in the RCR states, that it's a bad idea to introduce this
change in Smalltalk because it would break too much code. The situation in
Ruby is similar.

Is it?

I wwnt grepping through ruby 1.9 CVS (ok its a month or two old version)
looking for some code that will break.

I don't believe _anything_ will (currently) break.

--
John Carter

The Cybernetic Entomologist - (e-mail address removed)

http://geocities.yahoo.com/cy_ent

I'm becoming less and less convinced of humans as rational beings.
I suspect we are merely meme collectors, and the reason meme is only
kept on to help count our change.
 
C

Cyent

A simple example of "wrong" behavior (using the nil returns of !
methods which is another can of worms :)

class NilClass
def method_missing(sym, *args)
nil
end
end

x = "Pat"
x.strip!.upcase!

#use x in some important way....
# this now fails silently, the expectation
# was for an upcased string with no "extra" white space....
puts x

That's not a failure, thats a confirmation.

You asked for an upcase string with no whitespace. You have got one.

Wheres the problem?

You didn't ask for a non-empty string...


--
John Carter

The Cybernetic Entomologist - (e-mail address removed)

http://geocities.yahoo.com/cy_ent

I'm becoming less and less convinced of humans as rational beings.
I suspect we are merely meme collectors, and the reason meme is only
kept on to help count our change.
 
F

Florian Frank

Cyent said:
Unfortunately rubies run time switches are bug compatible with perl's.
But unlike Perl, Ruby doesn't require warning switches to gloss over bad
decisions in its design process. We shouldn't make these in Ruby after
the fact, should we?

I never use warning switches in Ruby without having any adverse affects
from it. I wouldn't advice anyone to do that in Perl. In Perl these
switches are a real plague, especially because of false alarms: That
explains the invention of the "use warnings" and "no warnings" pragmas.

One question, I have left: Why do you hate Ruby so much?
 
T

Thomas Adam

--- Cyent said:
If you read "man perl" and scroll down to the BUGS sections it
says...

I fail to see why you're comparing the two, when perl's use of "-w" and
"use warnings" as a pragma, are different to how Ruby handles such
things.
BUGS
The -w switch is not mandatory.

Anybody who is vaguely worried about bugs in their code whether they
are
running perl or ruby should _always_ use the -w switch.

As a guide, perhaps, but not because their program _might_ break -
that's why UnitTest is such a good thing to use.

-- Thomas Adam

"The Linux Weekend Mechanic" -- http://linuxgazette.net
"TAG Editor" -- http://linuxgazette.net

"<shrug> We'll just save up your sins, Thomas, and punish
you for all of them at once when you get better. The
experience will probably kill you. :)"

-- Benjamin A. Okopnik (Linux Gazette Technical Editor)





___________________________________________________________
Yahoo! Messenger - want a free and easy way to contact your friends online? http://uk.messenger.yahoo.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,774
Messages
2,569,599
Members
45,163
Latest member
Sasha15427
Top