43 comments

[ 2.7 ms ] story [ 89.3 ms ] thread
This is very consultant-y code. It's great billable time to have another reason to take 1 line of code with a predicate and "refactor" it into a broccoli floret of mock classes and exception handlers. But really, nothing insulates a competent programmer from needing to know what a library is going to return.
Exactly, they have traded .nil? for .present? and added complexity.
(comment deleted)
When I first looked at Haskell, the 'Maybe' concept was new to me and seemed like a useful concept for other languages. http://haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0...
Haskell is compiled. As I understand it, much of the point of Maybe is that it provides a formal language for expressing and manipulating unhandled edge cases. Then the compiler can spot those unhandled edge cases at compile time and make sure you handle them at one level of scope or another.

In Ruby the concept seems less useful because there is no compiler.

There are also interpreters for Haskell. Like hugs. And Maybe stays useful.
It has nothing to do with compilation. It has to do with static typing -- that, before ever evaluating something, you typecheck it. It is perfectly feasible to interpret a statically typed language -- SML/NJ for example, includes an interpreter for SML. However, statically typed languages are generally easier to fully compile than dynamic languages; SML/NJ also includes a compiler for SML.
Exactly. You can pass Maybe objects in Ruby as a convention, but the extra layers of indirection probably aren't buying you much. There is no way to enforce the contract.
That's not the point, the point is that Maybe is the right way to handle what nil (or None or null) fails to handle properly. Scala gets it right too, IIRC, with Nullable.

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.

Maybe is really just a special case of Either, where it optimizes for the situation where the second return type is "failure." The type of Either is necessary because proper type theory requires a container type to wrap two types. This is immediately obvious if you try to write the type signature for any function which has a return arity of one.
Maybe is still useful outside of compiled language because it is also a way of composing and combining operations that might fail. The catch is that a great deal of its usefulness (in Haskell &al) comes from static typing, because any function which could return nil would return Maybe<SomeType> instead of SomeType, so you are forced to check against Nothing in order to have a valid program, thereby eliminating any possible NullPointerExceptions from your program. (The monadic syntax Haskell/Scala/&c provide makes this less tedious than it sounds.)

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.

Vala does something like that. If you have a method that returns type X, it always has to return an X - not null. The type of "an X or null" is indicated as X?. It makes you realize, "hey, this could be null, am I handling those?"
There is a slight advantage to Haskell/Scala/Caml's Maybe versus the nullable type system used in Vala and Groovy and so forth. If you have a hash table whose values are (e.g.) Integers, looking up a key would return a Maybe<Integer> (because there's no guarantee the key exists.) If you wanted to have null values in the hash table, then the values would be Maybe<Integer> and looking up a key would return a Maybe<Maybe<Integer>>, which could be Nothing (because the key was not present in the table) or Some<Nothing> (because the key was present and a null value was stored in the table) or Some<Some<x>> where x is an Integer. As far as I know, nullable type systems don't allow Integer?? as a type. Still—it is a huge step above the nothing you're offered in other languages.
(comment deleted)
I think this is mostly a workaround for a huge flaw in most languages—especially one as dynamic as ruby. Nil should accept any method and return nil.

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
In Ruby nil can receive any method and return nil:

  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/

andand doesn't seem any different than using try(:method), which is defined in Rails 2.3+ for the Object class:

  > nil.try(:name)                     #==> nil
  > User.first.try(:name)              #==>"john doe"
  > nil.try(:name).try(:upcase)        #==> nil
  > User.first.try(:name).try(:upcase) #==>"JOHN DOE"
Thanks for pointing out the try method (it is important to note that it is avaible to anyone using Active Support, not only Rails apps).

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...

Thanks for the links. Some ruby food for thought . . .

  > x = nil || 5                   #behaviorally equivalent to elvis?
  > [nil,4,3].compact.map(&:to_s)  #null-ignored scope
You are welcome. The second line is the essential notion of Maybe monad as a restriction of the Sequence monad (Some of x works like [x] and None works like [], using ruby's [] notation for lists).
(comment deleted)
It's a question of taste. Some people's taste works like this: If it's in Rails, it must be the right thing to use. Other people's taste works like this: #try is simpler than #andand, and simpler is better. Others prefer andand's more natural syntax regardless.

For 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.
(comment deleted)
(comment deleted)
Sure, it makes some edge cases harder to debug, but not by very much.

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
    }
True, that's actually quite a tricky bug. However, that's also a flaw in Cocoa, I think. NSOrderedSame should not evaluate to a falsy value. Rather, the enum should start at 1.
Agreed. I think the reason it starts at -1 is to make sorting easier.

I usually use a category for string comparison so I don't have to think about it.

> Nil should accept any method and return nil.

That sounds a bit like the Maybe monad in Haskell.

Except very broken without the static analysis.
And it's implicitly everywhere.
I don't think that nil accepting any method and returning nil fixes this problem at all. It certainly shifts it. Sometimes enough to stop it from biting you, but only by accident. Other times, the shifting just makes it that much harder to figure out what is going on.

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.

Ugh, no. I've run into several situations in ObjC where I forget to alloc a NSArray or NSDictionary into an ivar, and then I go and put objects in it, and get really confused later when I query the array/dict and find nothing in it... That's one behavior of ObjC I absolutely hate. I'd rather a quick segfault that'll tell me the problem in 2 seconds vs. staring at the code for an hour trying to figure it out.
I commend the author for re-implementing basic typing for Ruby, but this example completely misses the point. If the client to User asks whether a user is an admin of a project before asking if it is a member of that project, the client code is broken already.
I'm a big fan of Lua's use of nil and the simplified error handling (No exception 'types') in that language. I think the problem with 'typing' Exceptions is that it means you are using it to record and pass describable\known state similar to return, instead of being reserved for unknown state. For indicating predictable failure states without using exceptions, I think multiple return values is a much better paradigm. Here's what happens when you open a non-existent file in Lua:

    =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-returns
This also works really well when combined with the assert function. Any function that returns nil and an error on failure can be wrapped in assert (which is just a normal function that takes a value and a message, and raises an error with the message if the value is nil or false).

If 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.
This argument just seems like a step along the path to static typing. If you're using Ruby, haven't you already decided that you prefer the simplicity of a dynamic language to the safety of static types? So why start to add half-baked typing to your code? It seems like it just puts you in an uncomfortable middle ground.
(comment deleted)
I think one's opinion of the final solution in the article boils down to if you would prefer to raise (or throw) an exception if there desired value is not found (i.e. like in Java when a file is not found), or you would rather return an error value (i.e. NULL in C from fopen etc).

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.

Part of the problem stems from the calling semantics of the language, namely when the operation hangs off of an object. Broadly speaking, functional languages avoid that manifestation.

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.

Having read the comments, I havn't seen what I find to be the gravest problem with this article - the poor OO practices of the author.

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'.

Don't these methods only work in the presence of an instance of a user as well?
I can't comment on Ruby, but as the author decided to throw in a "...AttributeError in Python" reference I figure I have two cents to add.

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?