Is ruby's multidimensional array out of bounds behaviour consistent? - ruby

If I have a multidimensional array, I can exceed the bounds in the final dimension and get a nil return, but if I exceed the bounds of a non-final dimension, I receive an error. Is this by design, and if so what is the reasoning?
> ar = [ [00,01,02], [10,11,12], [20,21,22] ]
=> [[0, 1, 2], [10, 11, 12], [20, 21, 22]]
> ar[2][2]
=> 22
> ar[2][3]
=> nil
> ar[3][2]
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):32
from :0
I understand why this is happening, but why isn't nil[] defined to return nil?

In Ruby, there are no multi-dimensional arrays. What you have there is an array, containing arrays as elements. So if you get the first "dimension", you get another array back (or nil if you exceeded the bounds of the outer array).
nil is an object of NilClass that has a finite (and small) set of defined methods. And the [] method, that is called when you use the whatever[:foo] syntax is just not defined on NilClass. Thus it can't return anything.
Generally, it wouldn't make sense to define all possible methods on nil as it would confuse people even more and would introduce a plethora of hard-to-detect bugs.
However if you know what you are doing and are willing to deal with the implications, you can use the try method that is defined by some frameworks (e.g. ActiveSupport for Rails) but is not part of Ruby itself. It catches the NoMethodError and returns nil instead. In your case you could use
> ar[2].try(:[], 2)
=> nil
However, this is generally discouraged as it makes things harder to debug. Instead you should check the bounds before trying to access the array (e.g. by using array.length) or by using include looping constructs like ar.each {|e| puts e}.

I don't know if Matz ever documented why the NilClass is designed the way it is. If it's not so then we can only guess. My guess is that it is based on the behavior of Smalltalk.
nil could either be message eating or exception throwing. Ruby has an exception throwing nil object.
If you have a message eating nil then it's difficult to determine the object that was the first one to return nil in a chained call like arr[1][2][3]. I don't know how often this really is an issue but it seems to be a valid point. As a counterexample Objective-C seems to do just fine with a message eating nil.
You can patch NilClass to become message eating
class NilClass
def method_missing(*)
nil
end
end
or for arrays only
class NilClass
def []
nil
end
end
both makes nil[] return a nil and either can break existing code
nil[][][][]
=> nil

Related

Understanding strange output in multidimensional array

I am new to ruby and I was trying to iterate over a 2d array. I made a mistake in my code. From that mistake, I noticed some unexpected output.
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
i = 0;
s.each{
|array| so = array[i] # pin
puts so[i]
}
Due to #pin, if i = 0, output is h t r. i = 1 gives w h r. i > 1 gives an error:
C:/Ruby.rb in `block in <main>': undefined method `[]' for nil:NilClass (NoMethodError)
from C:/Ruby.rb:3:in `each'
from C:/Ruby.rb:3:in `<main>'
If I use |array| so = array # pin, then my code does not produce strange output. I'll just fix the remaining stuff to make my code iterate for all values that 'i' can have.
Please explain this.
PS: Working code is here
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
s.each{
|array| so = array
array.each{
|str| puts str
}
}
For each type of sandwich, when i is 0, so is the 1st element, which is the meat. so[0] is the first letter of the meat.
When i is 1, which is the 2nd element, which is the cheese. so[1] is the second letter of the cheese.
When i is 3, there is no third component to the sandwich. so so is nil. so[2] is asking for the nil[2].
nil is a class, like everything in ruby. But nil does not implement the [] method, as arrays and other classes that implement the Enumerable module do.
Since nil does not support the [] method, then you get the undefined method error.
Even operations that are built into other languages, like +, [], and == are methods that can be overridden in Ruby.
To understand exactly what's happening, try this bit of code:
class NilClass
def [] (i)
nil
end
end
Executing that will open up the existing NilClass, and add a method called []. Then you can do nil[1] and it will return nil. Changing an existing class like this is known as monkey patching in the Ruby world.
When you ask for so[2] you are actually asking for the third element, and if it doesn't exist, you'll get an error.
I recommend structuring your blocks like so:
s.each do |pair|
puts pair
end
Note the do/end instead of {} and the placement of the iteration variable inline with the each. Also note that this is equivalent to your "working code" so you don't need the extra iterator in this case.

Couldn't understand the difference between `puts{}.class` and `puts({}.class)`

As the anonymous block and hash block looks like approximately same. I was doing kind of playing with it. And doing do I reached to some serious observations as below:
{}.class
#=> Hash
Okay,It's cool. empty block is considered as Hash.
print{}.class
#=> NilClass
puts {}.class
#=> NilClass
Now why the above code showing the same as NilClass,but the below code shows the Hash again ?
puts ({}.class)
#Hash
#=> nil
print({}.class)
#Hash=> nil
Could anyone help me here to understand that what's going one above?
I completely disagree with the point of #Lindydancer
How would you explain the below lines:
print {}.class
#NilClass
print [].class
#Array=> nil
print (1..2).class
#Range=> nil
Why not the same with the below print [].class and print (1..2).class?
EDIT
When ambiguity happens with local variable and method call, Ruby throws an error about the fact as below :
name
#NameError: undefined local variable or method `name' for main:Object
# from (irb):1
# from C:/Ruby193/bin/irb:12:in `<main>'
Now not the same happens with {} (as there is also an ambiguity between empty code block or Hash block). As IRB also here not sure if it's a empty block or Hash. Then why the error didn't throw up when IRB encountered print {}.class or {}.class?
The precedence rules of ruby makes print{}.class interpreted as (print{}).class. As print apparently returns a nil the class method returns #NilClass.
EDIT: As been discussed on other answers and in the updates to the question, print{} it of course interpreted as calling print with a block, not a hash. However, this is still about precedence as {} binds stronger than [] and (1..2) (and stronger than do ... end for that matter).
{} in this case is recognized as block passed to print, while [] unambiguously means empty array.
print {}.class # => NilClass
print do;end.class # => NilClass
You are running into some nuances of Ruby, where characters mean different things depending on context. How the source code is interpreted follows rules, one of which is that {} is a closure block if it follows a method call, and otherwise a Hash constructor.
It's common throughout the language to see characters mean different things depending on context or position within the statement.
Examples:
Parens () used for method call or for precedence
print(1..5).class => NilClass
print (1..5).class => Range <returns nil>
Square brackets [] used to call :[] method or for Array
print[].class => NoMethodError: undefined method `[]' for nil:NilClass
print([].class) => Array <returns nil>
Asterisk * used for multiplication or splatting
1 * 5 => 5
[*1..5] => [1, 2, 3, 4, 5]
Ampersand & used for symbol -> proc or logical and
0 & 1 => 0
[1, 2, 3].map(&:to_s) => ["1", "2", "3"]
Or in your case, braces used for block closures or for a hash
... hope it makes sense now ...

`non-object-ness` of `nil` in ruby

From one of the online resource of Ruby I found the below statement:
The special object nil is, indeed, an object (it’s the only instance of a class called
NilClass). But in practice, it’s also a kind of non-object. The boolean value of nil is
false, but that’s just the start of its non-object-ness.
While nil responds to method calls as below,like other objects,what non-objectness author tried to say :
nil.to_s #=> ""
nil.to_i #=> 0
nil.object_id #=> 4
nil.inspect #=> "nil"
Can anyone help me here to understand the philosophy - non-object-ness of nil ?
nil is equivalent with null in other languages. Usually, null should not treated as a sane value.
However - as you may noticed - the syntax of Ruby language does everything over the method calls on objects, a lot more things than Python. Determining a sanity of values is a part of it.
Consider the following example
def foobar(arg)
if arg < 1
return nil
else
return "Oh, hi!"
end
end
res = foobar(rand(2))
puts res unless res.nil?
As you see, in the last line I check the nil-ness of the result with calling a nil? method. This is a most effective way to do it, because comparation operators can be overloaded and can do a very different things. The nil? returns with true only if the value can be treated as nil (usually, if the value is nil - but nil? method is overridable too, even if it is highly discouraged. Developers usually are not override this method).
Another useful property of nil it is has a to_s method, so you can x = "#{nil} and it results an empty string.
If nil weren't be an object, you cannot call nil? or other useful functions on that, but you can faced with a NullPointerException like in Java or a segmentation fault like in C/C++. And usually it is pointless.

Ruby block - NoMethodError: undefined method `-' for nil:NilClass

Getting started with Rails and I'm stuck with a block operation. I want to loop through a model and update integer values for a new field by calculating difference between two datetime fields. Doing this in the console:
MyModel.all.each do |m|
m.new_integer_field = m.existing_datetime_field - m.parent_object.existing_datetime_field
m.save!
end
The result is
NoMethodError: undefined method `-' for nil:NilClass
It works on one record if I do:
m = MyModel.find(1)
m.new_integer_field = m.existing_datetime_field - m.parent_object.existing_datetime_field
m.save
I guess it's a basic syntax thing, but couldn't find obvious explanations. Grateful to get some pointers forward.
The m.existing_datetime_field in one of the instances was nil and therefore the operation didn't work. You need to check for nil values if not sure:
MyModel.all.each do |m|
unless m.existing_datetime_field == nil
m.new_integer_field = m.existing_datetime_field - m.parent_object.existing_datetime_field
m.save!
end
As I already said probably one of the attributes of the instances are nil.
To check this use #inspect on the instances:
MyModel.all.each {|i| puts i.existing_datetime_field.inspect }
It seems one of these objects is nil and then the - method is not defined.
As you suggested you can check the operands before to calculate the differences.

Ruby Nil and Zero

What's the science behind the fact that the to_i method in Ruby's NilClass instances returns zero? Returning nil or raising an exception would not be more logical?
It fits the Ruby philosophy of permissiveness (as opposed to, for example, the strictness of Python):
nil.to_i #=> 0
"".to_i #=> 0
"123hello".to_i #=> 123
"hello".to_i #=> 0
As noted by Zabba, you can use Kernel#Integer(string) for strict conversion.
NilClass defines #to_i for the same reason it defines a #to_a that returns []. It's giving you something of the right type but an empty sort of value.
This is actually quite useful. For example:
<%= big.long.expr.nil? ? "" : big.long.expr %>
becomes:
<%= big.long.expr %>
Much nicer! (Erb is calling #to_s which, for nil, is "".) And:
if how.now.brown.cow && how.now.brown.cow[0]
how.now.brown.cow[0]
else
0
end
becomes:
how.now.brown.cow.to_a[0].to_i
The short conversions exist when only a representation is needed. The long conversions are the ones that the Ruby core methods call and they require something very close. Use them if you want a type check.
That is:
thing.to_int # only works when almost Integer already. NilClass throws NoMethodError
thing.to_i # this works for anything that cares to define a conversion
to_i means "convert me to an integer if you can".
If you want "if you're very much integer-like, give me your integer value, else give a NoMethodError", then use .to_int.
There's another question that asks about the difference between to_i and to_int, to_s versus to_str, etc. Let me know if you'd like me to find it for you.
The protocol of to_i says that you must return an Integer and you must not raise an exception. Both of your suggestions violate at least one of those rules. So, no, those would not only not be more logical, they would be simply invalid.
Note, however, that nil does not respond to to_int. If it did respond to to_int, that would, indeed, be "illogical".
If you happen to be in Rails then you can use try:
nil.to_i # => 0
nil.try :to_i # => nil
A concise Ruby 2.3+ method for returning nil for nil.to_i instead of 0, is to use the safe navigation operator:
irb(main):001:0> nil.to_i
=> 0
irb(main):002:0> nil&.to_i
=> nil

Resources