If you gaze into nil, nil gazes also into you
robots.thoughtbot.com
robots.thoughtbot.com
This is the pattern that Objective-C embraces, and it works very well. You can chain method calls to nil and get nil as the result of the expression, should any method return nil. You end up with cleaner, more readable code. Sure, it makes some edge cases harder to debug, but not by very much. In this case, the benefits far outweigh the code. You'd simply end up with:
def admin_of?(project)
membership_for(project).admin? || false
end
Additionally since nil is falsy, you could even skip the `|| false`, and any `if u.admin_of? p` statement would still work. (Not recommended, but just pointing it out.) def admin_of?(project)
membership_for(project).admin?
end class NilClass; def method_missing(*args); nil; end; end
The problem? It makes code fail late and siltently, which can happen with a simple mispeling of key names on hashes.In ruby, Andand allows you to solve this more locally: http://andand.rubyforge.org/
> nil.try(:name) #==> nil
> User.first.try(:name) #==>"john doe"
> nil.try(:name).try(:upcase) #==> nil
> User.first.try(:name).try(:upcase) #==>"JOHN DOE"It is mildly different (syntax mostly). All of these are attempts to recreate the elvis operator tha Groovy[1] (nowadays present as the The Existential Operator in Coffescript[2]) have.
For better chaining, the Maybe monad can be implemented in Ruby[3]. However they really shine on Haskell and Scala because they have, respectively, do notation and for comprehensions, which effectively work (on this restricted case of handling nulls) as a scope were nulls are ignored.
Doing this with ruby is possible, but requires AST metaprogramming[4], which is the sort of thing that LISP macros do (and is quite suitable to accomplish in LISP[6], as it is homoiconic[5], while Ruby isn't).
[1] http://groovy.codehaus.org/Operators#Operators-ElvisOperator...
[2] http://jashkenas.github.com/coffee-script/#operators
[3] http://pretheory.wordpress.com/2008/02/14/the-maybe-monad-in...
[4] http://metaphysicaldeveloper.wordpress.com/2010/10/31/rubyun...
[5] http://en.wikipedia.org/wiki/Homoiconicity
[6] http://onclojure.com/2009/03/06/a-monad-tutorial-for-clojure...
> x = nil || 5 #behaviorally equivalent to elvis?
> [nil,4,3].compact.map(&:to_s) #null-ignored scopeFor example, I prefer #andand's syntax for methods with parameters:
User.first.andand.max_attempts = 5
Phone.first.andand.dial(last_number)
#andand handles methods with blocks: Supervisor.first.andand.oversee do
...
End
foo.andand.into { ... } is a common pattern, so #andand bakes it in: Student.where( ... ).first.andand { |valedictorian| ... }
For some strange reason, I feel like I've been using it longer than anybody alive on the planet.One of the edge cases that I've recently seen in some code is string comparison:
if ([myStr caseInsensitiveCompare:@"OtherString"] == NSOrderedSame)
{
// Do something
}
If myStr is nil, caseInsensitiveCompare is going to return nil (=> 0), which is the same as NSOrderedSame. In these cases, you either have to check for nil first or swap the variables: if ([@"OtherString" caseInsensitiveCompare:myStr] == NSOrderedSame)
{
// Do something
}I usually use a category for string comparison so I don't have to think about it.
That sounds a bit like the Maybe monad in Haskell.
Fundamentally, if you're not explicitly aware that something might be nil you won't always be making the right choices. The Objective-C behaviour just allows you to get lucky some of the time. Frankly I'd prefer if it just blew up in my face immediately.
http://github.com/raganwald/homoiconic/blob/master/2009-02-0...
In Ruby the concept seems less useful because there is no compiler.
1. A "null" instance of one type should not be conflated with a "null" instance of a separate type. 2. By type-wrapping in a Maybe, you declare where you need to be able to handle nulls and where you are free to ignore them (but can never pass them in). You confine the null to specific regions of your code. 3. You force your code's clients to think about the null case wherever you make it visible.
However, you could still make use of the composition operations in dynamic, untyped languages, and simply use them as a toolkit for chaining together operations which could possibly return nil. You don't actually have any static guarantees like you have in a typed case, but it's possible to imagine use-cases where the combining operations are useful enough to reimplement in a dynamically typed language. I suspect that a dynamically typed language that somehow lets you use some kind of monadic notation would benefit from this (e.g. perhaps through the use of Lisp macros), but without extra language support, it probably would just be tedious and verbose (e.g. in Ruby.) I'd be happy to be proved wrong, though.
=io.open 'sdfasdf'
nil sdfasdf: No such file or directory 2
The first value returned is nil, the 2nd value is a string containing an error message, and the 3rd is an integer error code. This allows you to write most code using 2 state boolean logic: f=io.open 'sasdasdfsf'
if f then -- nil is a boolean 0
whereas in Python, one often has to reason about 3 states by using the try\except blocks. Even though you are not using typed Exceptions, multiple return values does not discard any state about the error if it is desired: f, err = io.open 'sasdasd'
print(err)
I believe this also the design decision Google's Go language has made: http://golang.org/doc/effective_go.html#multiple-returnsIf the function returned a value (besides nil or false), then assert merely returns the value. If the function returns nil or false, then assert raises an error, and the error message is used as the error in the assert. For example:
> = assert(io.open("hello"))
file (0x8b9f658)
> = assert(io.open("goodbye"))
stdin:1: goodbye: No such file or directory
stack traceback:
[C]: in function 'assert'
stdin:1: in main chunk
[C]: ?
This makes it possible to fail early when you don't need or want to do full error handling, or to use advanced logic if you need to recover from the error - without a try/catch statement. Effectively, it makes exceptions optional.Unfortunately the author is out of their depth. Why are they checking for specific attributes or assuming objects are an instance of a particular class? This violates the spirit of Python. Indeed, from the Python glossary:
(Duck typing is a) Pythonic programming style that determines an object's
type by inspection of its method or attribute signature rather than by
explicit relationship to some type object ("If it looks like a duck and
quacks like a duck, it must be a duck.") By emphasizing interfaces rather
than specific types, well-designed code improves its flexibility by
allowing polymorphic substitution. Duck-typing avoids tests using type()
or isinstance(). Instead, it typically employs the EAFP (Easier to Ask
Forgiveness than Permission) style of programming.
The author even misinterprets the first reference they provide! How outrageous is that? The author links to a blog comment as "These errors are one of the largest sources of bugs.", but the actual link _explicitly states_: The nowhere-near-ready-for-peer-review numbers I've seen suggest that
something like 70% of bugs in Java manifest to the programmer as
NullPointerExceptions.
These bugs _manifest_ using some language-specific exception, but clearly the actual bug is a different kettle of fish. It could be absolutely anything; poorly specified interfaces, well-specified interfaces that are called badly, some lower-level exception getting silently caught, inconsistent state. What does null, nil, None, NULL, whatever, have to do with this?This is just creating a specific proxy object to return a more specialised error condition rather than just returning nil and making the user of the function guess at what could have gone wrong.
I could see this sort of proxy object mechanism being extended so that you could add retrying mechanisms to the code.
The other part of the problem stems from handling nils when one is given them, either as the result of an operation, or the arguments to one. Either way, this necessarily needs to be a context-specific decision.
Seriously. If you have a family of methods on a user that only work in the presence of an instance of a project, then the first thing you ask yourself should be 'hey, maybe these methods belong on the project instead' and not 'hey, lets reinvent nil'.