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
Ruby on Rails:How to Implement Cancel Button in Form_Tag
What Purpose Can Anonymous Modules Serve
Compute Geo Distance in Elasticsearch
How to Trigger Mouse Event in Capybara Test
Zip Up All Paperclip Attachments Stored on S3
Rails Routing: Giving Default Values for Path Helpers
Cloning an Array with Its Content
Getting Remove_Entry_Secure Error While Using Ruby Application
How to Get Ruby-Debug-Ide to Work
How to Properly Test Cancan Abilities with Rspec
Understand "Current_User" Concept When Creating a Login Session in Ruby
Should Rbenv Be Installed System-Wide, or at a User Level
Heroku Won't Reset My Database
Stub Method Only on The First Call with Rspec
"No Available Formula for Gcc46" While Installing Ruby 1.9.3 on Os X with Rvm: