> Now if your code is playing with objects that quack like a duck and you end up with a nil instead of a DuckLike object, your nil object will be able to quack a sensible default value (or raise an error if you prefer).
I never knew this but it will definitely come in handy.
begin
entry.at('description').indx_txt
rescue NoMethodError
end
? With the generic rescue you will always get nil back. With #andand you get nil from nil and a NoMethodError from objects, which is usually what you want.
Also, if #index_text is correctly spelled but it raises a NoMethodError, you're silently discarding it again. For this reason, I avoid silently discarding exceptions even when I think I know what I'm doing. If I'm wrong, I'm in a lot of trouble...
> I never knew this but it will definitely come in handy.
It's also pretty dangerous if you don't have a very good test coverage (and good tests), because it hides potential bugs. And it can break code which relies on the default behavior of `nil` (or other built-in objects).
That's one of the biggest issues I have with monkey patching of builtin classes in Ruby or Smalltalk: it's not scoped/namespaced.
edit: by comparison, Haskell's operators redefinition is scoped, so though you can redefine `+` as `-`:
Prelude> let (+) = (-) in print (3+5)
-2
this redefinition will only span your module and won't affect third-party library (unless you export the change and they import it for course).
That's one of the biggest issues I have with monkey patching of builtin classes in Ruby or Smalltalk: it's not scoped/namespaced.
In Smalltalk, the right thing to do is use a subclass. Have that function return an instance of a subclass of UndefinedObject that's tailored to the context. Instead of returning nil, return a NotApplicable instance. Then you can have it print out a locale appropriate 'n/a' message when you need to display the value.
That's a potential solution in any language (you don't even have to extend a none/nil/whatever object), but it doesn't help interact with standard or third-party libraries which return `nil` already.
My stock solution to libraries with, let's call it, "substandard polymorphism" -- facades and/or wrappers. This often lets you put all of your conditional logic for dealing with the errant library in just one place. It's better than nothing. Sometimes, it's a lot better.
No, it's a terrible idea. You have no scoping and the nil object running around all parts of the system with you have no way of telling what's going to happen when you start redefining it for your sole and singular use.
The .andand hack is at least arguable, inasmuch as it adds behavior to nil itself that is at least applicable to nil, rather than "quacking" or anything else that has nothing to do with nil.
You are also begging to create a fragile, magical system that will randomly throw errors and die in strange places that seem to have nothing to do with the error itself or can't possibly happen. I speak from experience. Do not hide the fact you got a null when you didn't expect. Basically, your program is still going to crash, it's only a matter of when it goes and how much data it corrupts before it happens. You want it to die sooner, before it scrambles anything more. (Again, using "andand" is different as it shows that you were explicitly concerned about getting a null back and are doing what it takes to get the null out of the system; it's just refactored "if null then X else Y" which is fine.)
Suggesting that you rewrite nil for the purpose of hiding errors is an incredible immature thing to do; I'm sorry to have to call it out but given what it could do to people and how much of their lives could be wasted by following this suggestion it needs to be said strongly:
Do Not Do This.
(If Ruby could add scoped monkeypatching, the equation radically changes from "Do Not Do This" to "jerf is uncomfortable with this style but has no particular objections otherwise", which is a completely different thing; then I'd be expressing an opinion on style instead of warning you that you're going to create a nightmare system if you do that.)
(And yeah, I said null, not nil; that's because this applies to more than just Ruby and I wanted to keep it generic.)
It's a bad idea to put methods on nil willy-nilly. I see stuff like this in Smalltalk projects all the time. People like to put < and > on nil and think they're clever because they fixed their little routine that does a sort. What they don't realize, is that they've possibly broken something else that depends on the Does Not Understand exception as part of its normal operation. (Yes, remember there's only one nil object, and you have no comprehensive list of what implicit contracts it has to fulfill in the rest of the code base. Certain proxy mechanisms can break. I know of another mechanism in one Smalltalk's streams that would break as well.)
The correct way to replace your nil checks with polymorphism is with the MissingObject pattern. Languages with explicit types should give you this for free. I think that even duck-typed languages should do this. (Every time you define a class Foo, you get MissingFoo automatically.) They can often be provided by libraries.
It's not just that. One should replace conditionals with polymorphism. It's just that, in this case, not enough thought is given to how. Namely -- how can I accomplish this without possibly breaking lots of other things?
Another option that I'm really warming to is to put it right in the type system whether or not you might have "Nulls". You can sort [Int] and you can sort [Maybe Int] if you provide the correct sort functions, but either way, there's no surprises. There simply will not be a "null" in an [Int].
While I'm using Haskell's notation, it seems likely that pretty much any strongly typed language could do something like this, at least ad hoc. (That is, Haskell hasn't actually written this into the typing system, it's just a direct use of the type system that it already had. But even a C++-like language could probably put this in there if it was written that way from day one; arguably, it's allowing the nulls to exist in the first place that is actually the hack.)
> it seems likely that pretty much any strongly typed language could do something like this
No. Or more precisely, in a dynamically typed language (which may or may not be strongly typed) there's no point in doing so, it won't buy you anything expressivity or correctness over nullable references.
Pretty much any statically typed language would benefit from making names/references non-nullable by default and moving nullability into the type system yes.
> it's allowing the nulls to exist in the first place that is actually the hack
How is it a hack? It's trivial to implement, but there's nothing hacky about it.
Type system hack, not code hack. Certainly the implementation is trivial, yes. Even under the weaker type systems of C++ or Java, having this magical, undeclared value that is also a Circle and a Car and an Account and so on is pretty dirty from the type point of view.
Haskell of course has its own troubles with bottom, and I note some people think we should be doing away with that, too.
And I call that a hack, especially when I don't think it was done on purpose or with thought, but simply because "that's how it needs to work". The fact that it wasn't intended as a hack doesn't make it not a hack; I'm sure that by 2050 we'll be looking back on types that could be magically NULL in much the same way I currently curse at SQL every time something generates "SELECT * FROM blah WHERE value IN ()". ("Damn it, SQL, it's just an empty list and always false, what's the damn big deal?! So very 70s...") There's a whole slew of things like that from older technologies; someday I really ought to make a list.
It lets you write cleaner code in situations when you don't care if an object is empty data or a nil. Avoiding the "whiney nils" ruby has a reputation for.
20 comments
[ 4.9 ms ] story [ 34.2 ms ] threadI never knew this but it will definitely come in handy.
My favorite is the andand gem: http://github.com/raganwald/andand/tree/master
It lets you turn code like this..
..into code like this..Also, if #index_text is correctly spelled but it raises a NoMethodError, you're silently discarding it again. For this reason, I avoid silently discarding exceptions even when I think I know what I'm doing. If I'm wrong, I'm in a lot of trouble...
It's also pretty dangerous if you don't have a very good test coverage (and good tests), because it hides potential bugs. And it can break code which relies on the default behavior of `nil` (or other built-in objects).
That's one of the biggest issues I have with monkey patching of builtin classes in Ruby or Smalltalk: it's not scoped/namespaced.
edit: by comparison, Haskell's operators redefinition is scoped, so though you can redefine `+` as `-`:
this redefinition will only span your module and won't affect third-party library (unless you export the change and they import it for course).In Smalltalk, the right thing to do is use a subclass. Have that function return an instance of a subclass of UndefinedObject that's tailored to the context. Instead of returning nil, return a NotApplicable instance. Then you can have it print out a locale appropriate 'n/a' message when you need to display the value.
The .andand hack is at least arguable, inasmuch as it adds behavior to nil itself that is at least applicable to nil, rather than "quacking" or anything else that has nothing to do with nil.
You are also begging to create a fragile, magical system that will randomly throw errors and die in strange places that seem to have nothing to do with the error itself or can't possibly happen. I speak from experience. Do not hide the fact you got a null when you didn't expect. Basically, your program is still going to crash, it's only a matter of when it goes and how much data it corrupts before it happens. You want it to die sooner, before it scrambles anything more. (Again, using "andand" is different as it shows that you were explicitly concerned about getting a null back and are doing what it takes to get the null out of the system; it's just refactored "if null then X else Y" which is fine.)
Suggesting that you rewrite nil for the purpose of hiding errors is an incredible immature thing to do; I'm sorry to have to call it out but given what it could do to people and how much of their lives could be wasted by following this suggestion it needs to be said strongly:
Do Not Do This.
(If Ruby could add scoped monkeypatching, the equation radically changes from "Do Not Do This" to "jerf is uncomfortable with this style but has no particular objections otherwise", which is a completely different thing; then I'd be expressing an opinion on style instead of warning you that you're going to create a nightmare system if you do that.)
(And yeah, I said null, not nil; that's because this applies to more than just Ruby and I wanted to keep it generic.)
The correct way to replace your nil checks with polymorphism is with the MissingObject pattern. Languages with explicit types should give you this for free. I think that even duck-typed languages should do this. (Every time you define a class Foo, you get MissingFoo automatically.) They can often be provided by libraries.
While I'm using Haskell's notation, it seems likely that pretty much any strongly typed language could do something like this, at least ad hoc. (That is, Haskell hasn't actually written this into the typing system, it's just a direct use of the type system that it already had. But even a C++-like language could probably put this in there if it was written that way from day one; arguably, it's allowing the nulls to exist in the first place that is actually the hack.)
No. Or more precisely, in a dynamically typed language (which may or may not be strongly typed) there's no point in doing so, it won't buy you anything expressivity or correctness over nullable references.
Pretty much any statically typed language would benefit from making names/references non-nullable by default and moving nullability into the type system yes.
> it's allowing the nulls to exist in the first place that is actually the hack
How is it a hack? It's trivial to implement, but there's nothing hacky about it.
Haskell of course has its own troubles with bottom, and I note some people think we should be doing away with that, too.
It's not either. Types are simply implicitly defined as nullable, as they are in SQL.
I strongly agree. Adding stuff to nil can have unintended consequences:
http://github.com/raganwald/homoiconic/blob/master/2009-02-0...
instead of
if myobj && myobj.size == 0 then :blah end
you can write
if myobj.blank? then :blah end
makes me miss perl though