nil: historical, theoretical, comparative, philosophical, and practical perspectives Steel City Ruby Conf in August 2012: my first Ruby conference
Josh advocated monkey-patching of nil to remove a nil check in an if/else.

Hated!
Inspired!
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
Who said this in 2009?
null (1965)fundamental contributions to the definition and design of programming languages
![]()
The unexpected appearance of an interpreter tended to freeze the form of the language, and some of the decisions made rather lightheartedly for the ... paper later proved unfortunate. These included ... the use of the number zero to denote the empty list NIL and the truth value false. Besides encouraging pornographic programming, giving a special interpretation to the address 0 has caused difficulties in all subsequent implementations.
Who said this in 1979?
nil![]()
null vs. nilnull: billion-dollar mistake
![]()
nil: pornographic programming
![]()
nil and null in Ruby and other languagesnilWho has never seen this output when running a Ruby program?
nil.rb:2:in `<main>': undefined method `name' for nil:NilClass (NoMethodError)
Point of failure:
puts 'Dr. ' + person.name
(Trick question.)
C++ is unsafe: fails catastrophically on NULL dereference:
Segmentation fault: 11
Point of failure:
cout << "Dr. " << person->getName() << endl;
Java (like Ruby) is safe: checks for null dynamically:
Exception in thread "main" java.lang.NullPointerException
at Null.main(Null.java:10)
Point of failure:
System.out.println("Dr. " + person.getName());
nil's cousinsnil (Smalltalk, Lisp)null (Java)NULL (C)nullptr (C++)undef (Perl)None (Python)None (OCaml, Scala)Nothing (Haskell)Most of these engage in some form and extremeness level of pornography.
Of those, we focus on Ruby's nil.
Who does what to whom?
Who intends to do what?
Who should do what?
How does one do something?
How to believe someone has done the right thing?
nil example, revisitedFailure:
nil.rb:2:in `<main>': undefined method `name' for nil:NilClass (NoMethodError)
Code that crashed:
puts 'Dr. ' + person.name
Three cases:
nilnilnilProblem: forgot to initialize person as intended.
Solution:
nilnilNew code somewhere:
person = Person.new(:name => 'Smith')
Note: Person.new will never return nil if it succeeds at all.
nilProblem: person was intended to be nil, to indicate there is no longer a person.
Elaboration: there was a person in the room, but the person left, so person was set to nil, and there should be no greeting.
nil solutionSolution:
person could be nilnilnilNew caller code:
if person.nil?
puts 'I see nobody here'
else
puts 'Dr. ' + person.name
end
Note: putting in a nil check is not enough; must provide entire code path.
Problem: person was assigned some_method(...) at some point.
person = room.find_tallest_person
puts 'Dr. ' + person.name
New problem: locally, without further context,
person is ever intended to possibly be nilsome_method is responsible for giving us nil or non-nilnilThe big problem with nil: any assignment other than to nil itself or a constructor call might or might not result in nil.
nil checks?Solution (?):
nil?nil branchDrawbacks:
nil, since everyone will check anywayHow many people code in this way?
nil exceptions?Solution (?):
nil checksnilExample:
begin
# deeply nested code...
rescue NoMethodError => e
# restart the app at some good state
end
Ruby allows monkey-patching: modifying a class.
Solution (?):
class NilClass
def name
'Default Name'
end
end
person = nil
puts 'Dr. ' + person.name
Output:
Dr. Default Name
Drawbacks:
nil responsible for a person's name??Not a solution either:
class NilClass
def method_missing(*args)
nil
end
end
person = nil
puts 'Dr. ' + person.name.junk.nonsense.everywhere
Output:
nil-monkey-all.rb:8:in `+': can't convert NilClass to String (NilClass#to_str
gives NilClass) (TypeError)
from nil-monkey-all.rb:8:in `<main>'
Drawbacks:
nameputs person.name.junk.nonsense.everywhere would have succeeded, unintentionallyabsence of a Person
should not be the same concept as
presence of a NilClass
null and nil?Tony Hoare and John McCarthy knew how to do things right in the 1960s, but chose not to!
null for a statically typed language for efficiency and his convenience as a compiler writernil for a dynamically typed language for efficiency and his convenience as an interpreter writer"Those who cannot remember the past are condemned to repeat it." (George Santayana, 1905)
null and nilnull: replace with static type system as in functional languagesnil: replace with object-oriented design patternExplore both: duality between functional and object-oriented languages.
Start with functional.
Examples in Haskell syntax for convenience.
(Note: intended semantics is ML: Haskell's lazy types introduce complications.)
Primitive types:
example1 :: Int
example1 = 42
example2 :: Char
example2 = 'c'
example3 :: ()
example3 = ()
etc.
Tuples of type A and type B and ...
Most languages add syntactic sugar and semantics, as records or structs or classes.
type Person = (Int, String)
person :: Person
person = (42, "Sally")
example :: (Bool, Person)
example = (True, (42, "Sally"))
Real Haskell has records:
data Person = P { id :: Int, name :: String }
person :: Person
person = P { id = 42, name = "Sally" }
Type A or type B or ..., value tagged with which one it is.
Familiar from ALGOL (1960s), Pascal (1970s), etc.
data Color = Red | Blue
example1 = Red
example2 = Blue
data Bool = False | True
data Key = Name String | ID Int
example1 = Name "Sally"
example2 = ID 12
data Login = Guest | User Key
example1 = Guest
example2 = User (Name "Sally")
example3 = User (ID 12)
Function type from type A to type B:
addInts :: Int -> Int -> Int
addInts x y = x + y
greeting :: Person -> String
greeting person = "Dr. " ++ name person
Note: greeting never gets a null and never fails.
The type system guarantees that greeting can only be called on a valid Person.
In real life, need a way to express: x can be bound to a Person or to nothing.
Very easy: use a sum type!
data MaybePerson = Nobody
| One Person
-- OK
person :: Person
person = P { id = 42, name = "Sally" }
-- type error!
personFAIL = Nobody
-- OK, nobody
possiblePerson1 :: MaybePerson
possiblePerson1 = Nobody
-- Wrapped person
possiblePerson2 :: MaybePerson
possiblePerson2 = One (P { id = 12, name = "Jack" })
PersonFunctions can be defined to require a Person or MaybePerson:
greeting :: Person -> String
greeting person = "Dr. " ++ name person
-- will not type check
greetingForMaybePersonFAIL :: MaybePerson -> String
greetingForMaybePersonFAIL person = "Dr. " ++ name person
-- type checks
greetingForMaybePerson :: MaybePerson -> String
greetingForMaybePerson maybePerson =
case maybePerson of
Nobody -> "I see nobody here"
One person -> "Dr. " ++ name person
This technique does not prevent buggy code!
-- type checks, but compiler warns of missing case of Nobody
greetingForMaybePersonBAD :: MaybePerson -> String
greetingForMaybePersonBAD maybePerson =
case maybePerson of
One person -> "Dr. " ++ name person
-- run this code
greetingForMaybePersonBAD Nobody
Failure:
Exception: Non-exhaustive patterns in case
But at least the compiler warns you of code branches you were supposed to write.
Parameterized types (generics) use type variables such as a, b.
C++ templates, C# generics, Java generics, etc.
type NestedTriple a b c = (a, (b, c))
example1 :: NestedTriple Boolean Int String
example1 = (True, (42, "Sally"))
Maybe as parameterized typeThe static typing way to mitigate the nil problem:
data Maybe a = Nothing | Just a
Avoid having to create a special MaybeX wrapper type for every type we want to have a nil possibility for.
possiblePerson:: Maybe Person
possiblePerson = Maybe (P { id = 12, name = "Jack" })
Standard ML, OCaml, Scala also have this parameterized type, except that they use different words: Option instead of Maybe, None instead of Nothing, and Some instead of Just.
Maybe example: intention(Back to ethics.)
Every variable has a static type.
A type is an intention.
Maybe example: responsibilityEvery variable, because it has a declared type, forces an accessor to take responsibility for how to use it.
A variable not of a Maybe type can never be bound to Nothing.
The type checker of the compiler enforces everyone's responsibility.
Maybe example: meansMeans: static type checker of compiler.
Maybe example: trustTrust (for type correctness) is delegated to the compiler.
(But type correctness does not guarantee code correctness.)
Maybe example: actionType forces us to take action: unwrap a Maybe value to treat both cases.
But
Maybe typeMaybe is a tool, not a solution to programming.
Java and similar so-called statically typed languages: really duck typed languages!
nullnull quacks unconditionally by either a segmentation fault or an exceptionMaybe, conclusionObservations:
a, the type Maybe a generates its own unique Nothing: no global nilMaybe: back to historynull right in the 1960sMaybe in the 1970s and 1980sReminder: purpose of static type system is to aid human reasoning; not primarily for efficiency!
nilPossibilities:
Maybe wrapperMaybe for RubyMaybe-inspired Ruby librariesI have not used any of these examples, but should examine for correctness and usefulness
Exploit duality of functional and object-oriented design:
Original code:
def greeting(person)
if person.nil?
puts 'I see nobody here'
else
puts 'Dr. ' + person.name
end
end
person = nil
greeting person
New code:
class Person
# ...
def greeting
puts 'Dr. ' + @name
end
end
class NoPerson
def greeting
puts 'I see nobody here'
end
end
person = NoPerson.new
person.greeting
Workarounds:
Option at compile time: Hoare's concern in 1960s!nil-handling patternsnil handling is a real problemFranklin Chen
This slideshow: http://franklinchen.com/talk-on-nil
| Table of Contents | t |
|---|---|
| Exposé | ESC |
| Full screen slides | e |
| Presenter View | p |
| Source Files | s |
| Slide Numbers | n |
| Toggle screen blanking | b |
| Show/hide slide context | c |
| Notes | 2 |
| Help | h |