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