`nil.size` returns `0`. Is it ok?

Hi there!

In trying to answer the following question

I noticed that nil.size returns 0. Is this ok?
I think nil.size should return an error. This will also help to detect the problem in the above code more quickly, I think.

What about 1, or any other non-Collection object? Errors for all of them, or some of them are 0 and some of them produce errors?

hjh

I think all objects (metaclasses and all instances) except collection’s subclasses return 0.

(
[Object, Object.allSubclasses].flatten.do { |aClass|
	var x = aClass.size;
	if(x != 0) { [aClass, x].postln } 
}
)

SinOsc.kr.size
$1.size
\1.size
"".size

For other objects, 0 is better than 1 because it lets us know if the object is a subclass of Collection. However, nil or Nil should not return 0. It should return nil, a warning or an error message, I think.

Hm, I changed my thought:

The following example returns also 0:

"".size
[].size

Thus, other object should return nac: not a collection;
Nil or nil should return also nac, otherwise a warning or an error.

My feeling is: It’s important to measure the benefits against the cost.

After the proposed change, many calls to .size would have to be nil-guarded: something.notNil and: { something.size > 0 }. This would be highly disruptive (it’s easy to underestimate how disruptive this would be).

To be honest, I don’t see any upside to the change.

So I would, at this time, have to vote against it.

hjh

1 Like

If a variable like a is accidentally nil, the code a.size will return 0. This can cause a silent error: sclang will not show an error message, but the code will not work as intended.

The advantage of changing this behaviour, I think, is that this silent error can be prevented, and that this is more user friendly. Other than that, I see no other advantages… So if the cost is greater than the benefit, then yes, there is no need to change.

Can you give a specific case where it does not work as intended?

Certain methods apply to a wide variety of object types, do being the most common (also collect, select etc). Typically these are defined as no-ops for nil and that’s a good thing. I suspect you’re looking at the a.do in the other MIDI thread and thinking that users should be warned about trying to .do over nil, but actually they shouldn’t: the correct behavior of nil.do is to do nothing (no iteration – the user function never gets called).

hjh

In the thread I mentioned above there is ~samples.size - 1. I quote the part below:

The problem is that there is no ~samples in this code, but there is ~samples1. So ~samples.size will return 0. (I think this is a typo.) So the author of the code will expect that turning the knob will change the buffer, but it will not work as intended, because val.linlin(0, 127, 0, ~samples.size - 1) is val.linlin(0, 127, 0, -1).

I can’t remember if I’ve come across cases like this before, but I usually make a lot of typos, so I’m not immune.

Perhaps another way would be:

+ Collection {
    collectionSize { ^this.size }
}

This method would throw an error when called on a non-collection object (including nil).

size is a core method. To me it’s quite risky to change its meaning just because one user committed a typo one time.

hjh

1 Like

Thank you for the suggestion to change the method.

I also think the change could be very risky. Let’s see what other developers think about it…