From: Alex Young Date: 2011-10-04T19:08:39+09:00 Subject: [ruby-core:39922] Re: [Ruby 1.9 - Feature #5372][Open] Promote blank? to a core protocol On 03/10/11 22:25, Eric Hodel wrote: > On Oct 2, 2011, at 2:38 PM, Alex Young wrote: >> Eric Hodel wrote in post #1024462: >>> On Sep 27, 2011, at 6:52 PM, Alex Young wrote: >>> Can you show me a description of the opposite? >> >> What I mean by "in reverse" is that with the Null Object, we have an >> instance which silently does the right thing. We don't have to care that >> it's null, we just call methods on it like we would on a non-Null >> instance. >> >> With a #null? or #blank? method, we instead have a way to ask each >> instance directly whether it's null, without having to care about its >> class. If it quacks like a null, then it's null. > > I mean, on the C2 wiki or somewhere else on the internet. Can you show other languages that have benefited from a similar implementation? If there is such a document maybe it can help us understand. To my knowledge it's most similar to Either in Haskell, but you have to squint a bit to see it: http://haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/Data-Either.html If you renamed #empty? to #left? the similarity should be a little clearer. If you look at Perl 6, there's also a stack of similar-looking functionality around Mu, Failure and Whatever - specifically the .defined method is close to what I'm thinking, but they've taken it a *lot* further. > >>>> Because the core API commonly returns nil in error cases.. >>> >>> Can you show some examples? I don't seem to write nil checks very often >>> when using core methods, but maybe I am forgetting. >> >> Having a quick look over the core docs, there's quite a few in >> File::Stat and Process::Status, all the try_convert() methods, >> Kernel.caller, Kernel.system, arguably String#slice and Regexp#match >> (although I can't see the latter being reasonably alterable), and >> Thread#status at least. > > When does caller return a non-Array? $ irb ruby-1.9.2-p290 :001 > caller(22) => nil It's when the depth parameter exceeds the current stack depth. > >>>> case thingy >>>> when Blank >>>> # catch-all >>>> # other cases >>>> end >>> >>> What about: >>> >>> case thingy >>> # other cases >>> else >>> # catch-all >>> end >> >> Yep, that's another way to do the same sort of thing, but with a Blank >> or Null it's more explicit and more flexible. With a bare >> "case...else..." you have to handle both correct nulls and erroneous >> values in the "else" clause. With Null, you can leave the "else" clause >> purely for handling the error case, where you've somehow got a response >> you weren't expecting. I think it's clearer. > > The problem I see is that adding #empty? to every class is confusing. Part of that is down to the name. #null? is better because it doesn't imply that the receiver is a container. You'll notice that I *didn't* suggest an implementation for Symbol: sometimes it doesn't make sense for any instance of a class to be null. You could make the same argument about Numeric, but I've occasionally found treating #zero? as a null test to be useful in the past. > > Should File::Stat#empty? returning true to mean the file is empty? Or should it always return false to say "the file exists" I'd go for the latter, personally. > What would Process::Status#empty? mean? Would false mean that the program had exited non-zero or that the program had exited with any status? I mentioned upthread that it would be useful aliased to #exited?, but I'd really prefer it to test whether the process was actually running - from the documentation of #exited? it sounds like processes that segfault will cause #exited? to return false. > > Kernel#system and Thread#status return true, false, or nil, so combining "non-zero exit" and "command failed" into #empty? isn't clearer to read than 'if system(command) then � else abort "#{command} failed" end' Sure. I'd say Kernel#system is an interesting example, though. Say I was being implementing it as a third-party library, but with a twist: instead of returning nil on command failure, I want to capture some details about the failure and wrap them up in a hypothetical ProcessFailure instance. Some of the time, I don't care about the details of the failure, and other times I do, but in no case do I think of this as warranting an Exception. Now, if I say: class ProcessFailure def null? true end end then when I *don't* care which happened, either the command failing or it having a non-zero exit, I can just say: unless mysystem(foo).null? # it worked! end and when I *do* care, it's: unless (result = mysystem(foo)).null? # it worked! else # It didn't, so try to do something useful with the error details $stderr.puts result.to_s if result end Note that while it might make the conditionals cleaner here, I *can't* do the obvious thing of: class ProcessFailure < FalseClass; end because that's just not how booleans work. > While it might make String#split or Regexp#match and try_convert usage clearer, it adds much confusion otherwise. As I mentioned above, there are definitely cases where null? should never be true for a given class because if you have a value, it's not null by definition. It's simple enough to leave the default #null? -> false implementation in place for them. -- Alex