Why Does Ruby Use Nil to Name the Null Object

Why does Ruby use nil to name the null object?

Well, "nil" is the traditional name for the reified concept of "nothing" in Lisp and Smalltalk†. The word "null" is used as an adjective meaning "empty", as in "the null list," which is nil.

Meanwhile, "null" is traditionally a pointer value in C that signifies the pointer doesn't point to anything valid. It refers to the fact that the pointer is null (in the same sense that Lisp uses the word), but it came to be thought of as a value on its own.

Matz was a fan of both Smalltalk and Lisp, so he went with their terminology. There isn't really an important difference in meaning between the two terms — one is just C-ish.

Actually, "nil" existed in a lot more languages than just those. Even Algol-68, the great granddaddy of C, called it "nil". I'm not sure if C invented "null" as the name for the null reference or just popularized it, but I'm pretty sure that all the modern languages using that term got it from C.

In Ruby (1.9.3), why does nil respond to the comparison operator, `=`?

nil in Ruby is a singleton instance of NilClass, which inherits from Object. Object implements <=>, which has its behavior defined as:

Returns 0 if obj and other are the same object or obj == other, otherwise nil. 0 means self is equal to other. 1 means self is bigger than other. Nil means the two values could not be compared.

(See the documentation)

Thus, nil <=> nil returns 0 (they are equivalent), but nil <=> anything_else returns nil, which means "could not be compared".

In Ruby, it is expected that all objects respond to <=> (including nil), but for objects for which it is a nonsensical or undefined operation, the return value is nil, which may then be handled as the calling code best sees fit. In the case of Enumerable's operations like #sort, it raises an exception:

[1, nil].sort
# => ArgumentError: comparison of NilClass with 1 failed

But it needn't necessarily; you could implement your own sort which just moves unsortable values to the beginning of the list:

[1, nil, 2, 3, nil].sort {|a, b| (a <=> b) || -1 }
# => [nil, nil, 1, 2, 3]

Ruby nil-like object

This is a pretty long answer with a bunch of ideas and code samples of how to approach the problem.

try

Rails has a try method that let's you program like that. This is kind of how it's implemented:

class Object
def try(*args, &b)
__send__(*a, &b)
end
end

class NilClass # NilClass is the class of the nil singleton object
def try(*args)
nil
end
end

You can program with it like this:

fizz.try(:buzz).try(:foo).try(:bar)

You could conceivably modify this to work a little differently to support a more elegant API:

class Object
def try(*args)
if args.length > 0
method = args.shift # get the first method
__send__(method).try(*args) # Call `try` recursively on the result method
else
self # No more methods in chain return result
end
end
end
# And keep NilClass same as above

Then you could do:

fizz.try(:buzz, :foo, :bar)

andand

andand uses a more nefarious technique, hacking the fact that you can't directly instantiate NilClass subclasses:

class Object
def andand
if self
self
else # this branch is chosen if `self.nil? or self == false`
Mock.new(self) # might want to modify if you have useful methods on false
end
end
end

class Mock < BasicObject
def initialize(me)
super()
@me = me
end
def method_missing(*args) # if any method is called return the original object
@me
end
end

This allows you to program this way:

fizz.andand.buzz.andand.foo.andand.bar

Combine with some fancy rewriting

Again you could expand on this technique:

class Object
def method_missing(m, *args, &blk) # `m` is the name of the method
if m[0] == '_' and respond_to? m[1..-1] # if it starts with '_' and the object
Mock.new(self.send(m[1..-1])) # responds to the rest wrap it.
else # otherwise throw exception or use
super # object specific method_missing
end
end
end

class Mock < BasicObject
def initialize(me)
super()
@me = me
end
def method_missing(m, *args, &blk)
if m[-1] == '_' # If method ends with '_'
# If @me isn't nil call m without final '_' and return its result.
# If @me is nil then return `nil`.
@me.send(m[0...-1], *args, &blk) if @me
else
@me = @me.send(m, *args, &blk) if @me # Otherwise call method on `@me` and
self # store result then return mock.
end
end
end

To explain what's going on: when you call an underscored method you trigger mock mode, the result of _meth is wrapped automatically in a Mock object. Anytime you call a method on that mock it checks whether its not holding a nil and then forwards your method to that object (here stored in the @me variable). The mock then replaces the original object with the result of your function call. When you call meth_ it ends mock mode and returns the actual return value of meth.

This allows for an api like this (I used underscores, but you could use really anything):

fizz._buzz.foo.bum.yum.bar_

Brutal monkey-patching approach

This is really quite nasty, but it allows for an elegant API and doesn't necessarily screw up error reporting in your whole app:

class NilClass
attr_accessor :complain
def method_missing(*args)
if @complain
super
else
self
end
end
end
nil.complain = true

Use like this:

nil.complain = false
fizz.buzz.foo.bar
nil.complain = true

How to elegantly handle nil in Ruby

For starters, I would pick more expressive variable names because I am not sure what exactly each one represents.

There is a possibility that this query will return nil

This sounds like a great case for the Null Object Pattern. Essentially, whatever is returned from your query can be passed to the constructor of a class. That class can then determine what ought to be returned, whether it's simply the value of the query, or if the value of the query is nil, some predetermined value is returned. Employing this pattern usually means having a class such as NullBusID that represents, well, nothing.

For more on the Null Object Pattern:

http://confreaks.tv/videos/bathruby2015-nothing-is-something

https://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object

Is there a pattern for comparing potentially nil objects in ruby?

You could define a Foo#to_f method :

  • if @time is defined, it returns @time.to_f
  • if @time is nil, it returns minus infinity

This way, Foo.new(nil) is smaller than any other Foo.

require 'date'
class Foo
attr_accessor :time
include Comparable
def initialize(time)
@time = time && time.to_time # time can be nil, a Date or a Time
end

def to_f
@time ? @time.to_f : -Float::INFINITY
end

def <=>(other)
to_f <=> other.to_f
end

def to_s
@time ? @time.strftime('%Y-%m-%d') : 'nil'
end

alias inspect to_s
end

p Foo.new(Date.new) < Foo.new(nil)
# false

p Foo.new(nil) < Foo.new(Date.new)
# true

p [Foo.new(Date.today), Foo.new(nil), Foo.new(Date.new(2015, 3, 1)), Foo.new(Date.new(2018, 5, 14)), Foo.new(Time.now + 3600)].sort
# [nil, 2015-03-01, 2017-06-24, 2017-06-25, 2018-05-14]

Refactoring with Null object pattern

require 'date'

class NilTime
def to_f
- Float::INFINITY
end

def strftime(*)
'nil'
end
end

class Foo
attr_accessor :time
include Comparable
def initialize(time = nil)
@time = if time.respond_to?(:to_time)
time.to_time
else
NilTime.new
end
end

def to_f
@time.to_f
end

def <=>(other)
to_f <=> other.to_f
end

def to_s
@time.strftime('%Y-%m-%d')
end

alias inspect to_s
end

Ruby - Handling null objects properly

The problem is not the checks, you are doing that right. Anything that's not nil or false is true in ruby. It's that when you get an exception on the row that starts with "sp = .." the execution jumps to the resque block. You should restructure the code like this (I've removed the ensure clause because I do not know what it does). A good thing to do it's to rescue every specific type of exception in it's own row. by class name ex. NoConnectivityException => e (or what the class of the exception would be).

 begin
sp = SerialPort.new(@serial_device, @serial_bps, @serial_par, @serial_bits, SerialPort::NONE)
sp.print(command)
sp.close
say siri_output
rescue Exception => e
puts "Sorry, I encountered an error: #{e.inspect}"
puts "trying TCP"
begin
tcp = TCPSocket.new(@host, @port)
tcp.print(command)
tcp.close
say siri_output
rescue Exception => e
puts "Sorry, I encountered an error: #{e.inspect}"
end
end

For quick and sloppy programming you can do another thing, but it's not recommended and generally a pain to debug, as any error results in nil and is silenced.

sp = SerialPort.new(@serial_device, @serial_bps, @serial_par, @serial_bits, SerialPort::NONE) rescue nil
tcp = TCPSocket.new(@host, @port) rescue nil

This way you'd end up with either a SerialPort object or nil object in the sp variable, and the same for sp.

Ruby memoization and Null Object pattern

You could maybe crib from FalseClass and set the same operator methods on NoThing. But I'd hesitate to do so for a number of reasons.

Unlike some other languages, Ruby is very clear that there are a very limited set of things which are false, false and nil. Messing with that will probably lead to confusion and bugs down the road, it's probably not worth the bit of convenience you're looking for.

Furthermore, the Null Object Pattern is about returning an object that has the same interface as an object which does something, but it does nothing. Making it appear false would defeat that. The desire to write @thing ||= Thing.new clashes with the desire for a Null Object. You always want @thing set even if Thing.new returns NoThing, that's what Null Objects are for. The code using the class doesn't care if it's using Thing or NoThing.

Instead, for those cases when you want to distinguish between Thing and NoThing, I'd suggest having little method, for example #nothing?. Then set Thing#nothing? to return false and NoThing#nothing? to return true. This allows you to distinguish between them by asking rather than piercing encapsulation by hard coding class names.

class NoThing
def status
:cancelled
end

def expires_on
0.days.from_now
end

def gateway
""
end

def nothing?
true
end
end

class Thing
attr_accessor :status, :expires_on, :gateway
def initialize(args={})
@status = args[:status]
@expires_on = args[:expires_on]
@gateway = args[:gateway]
end

def nothing?
false
end
end

Furthermore, it's bad form for Thing.new to return anything but a Thing. This adds an extra complication to what should be a simple constructor. It shouldn't even return nil, it should throw an exception instead.

Instead, use the Factory Pattern to keep Thing and NoThing pure and simple. Put the work to decide whether to return a Thing or NoThing in a ThingBuilder or ThingFactory. Then you call ThingFactory.new_thing to get a Thing or NoThing.

class ThingFactory
def self.new_thing(arg)
# Just something arbitrary for example
if arg > 5
return Thing.new(
status: :allgood,
expires_on: Time.now + 12345,
gateway: :somewhere
)
else
return NoThing.new
end
end
end

puts ThingFactory.new_thing(4).nothing? # true
puts ThingFactory.new_thing(6).nothing? # false

Then, if you really need it, the factory can also have a separate class method that returns nil instead of NoThing allowing for @thing ||= ThingFactory.new_thing_or_nil. But you shouldn't need it because that's what the Null Object Pattern is for. If you really do need it, use #nothing? and the ternary operator.

thing = ThingFactory.new_thing(args)
@thing = thing.nothing? ? some_default : thing


Related Topics



Leave a reply



Submit