4 ms·
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 t
by SeoxyS 15y ago
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
- DanielRibeiro 15y agoIn 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/ http://andand.rubyforge.org/
- klochner 15y agoandand 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"
- DanielRibeiro 15y agoThanks 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%28%3F%3A%29 http://groovy.codehaus.org/Operators#Operators-ElvisOperator... [2] http://jashkenas.github.com/coffee-script/#operators http://jashkenas.github.com/coffee-script/#operators [3] http://pretheory.wordpress.com/2008/02/14/the-maybe-monad-in-ruby/ http://pretheory.wordpress.com/2008/02/14/the-maybe-monad-in... [4] http://metaphysicaldeveloper.wordpress.com/2010/10/31/rubyunderscore-a-bit-of-arc-and-scala-in-ruby/ http://metaphysicaldeveloper.wordpress.com/2010/10/31/rubyun... [5] http://en.wikipedia.org/wiki/Homoiconicity http://en.wikipedia.org/wiki/Homoiconicity [6] http://onclojure.com/2009/03/06/a-monad-tutorial-for-clojure-programmers-part-2/ http://onclojure.com/2009/03/06/a-monad-tutorial-for-clojure...
- klochner 15y agoThanks 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
- DanielRibeiro 15y agoYou 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).
- deleted 15y ago[deleted]
- raganwald 15y agoIt'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.
- deleted 15y ago[deleted]
- deleted 15y ago[deleted]
- Aqua_Geek 15y agoSure, 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 }
- SeoxyS 15y agoTrue, 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.
- Aqua_Geek 15y agoAgreed. 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.
- eru 15y ago> Nil should accept any method and return nil. That sounds a bit like the Maybe monad in Haskell.
- sausagefeet 15y agoExcept very broken without the static analysis.
- eru 15y agoAnd it's implicitly everywhere.
- jsankey 15y agoI 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.
- kelnos 15y agoUgh, 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.
- raganwald 15y agoThe Hopelessly Egocentric Blog Post discusses this anti-pattern: http://github.com/raganwald/homoiconic/blob/master/2009-02-02/hopeless_egocentricity.md http://github.com/raganwald/homoiconic/blob/master/2009-02-0...