Don't think you need not clean up your "nots"!
If you've read the original Ruby Primer, you'll remember the "hiring programmers" problem. It was one of the first places the Primer really pushed the reader to think about different pieces of logic in a single statement. Often when working with this sort of code, you'll run into negations using Ruby's bang operator (!
). Experienced programmers are trained to hunt for these wispy characters in code they read but even a master programmer can miss one during a debugging session after a long night of hacking.
An alternative to code like this: !customer.has_receipt? || !clerk.handles?(customer)
would be this: customer.not.has_receipt? || clerk.not.handles?(customer)
. The English word "not" helps us read this code as "this condition passes if the customer is missing her receipt or if the clerk cannot handle the customer." It's worth noting here that Ruby does have a "not" keyword, used as a prefix. If you don't consider this method more readable than the built-in keyword, just treat it as an interesting thought exercise.
Fill out the Not
class below, and use it to return an object capable of inverting calls to Object#not
. We'll use calls to smith?
as examples and in the tests. You'll see why in the next exercise.
Minor magic! It's almost as if we've added a new feature to the language. Ostensibly, we have -- we've changed every object in our program. Of course, one should be wary of wielding such power loosely. These sorts of sweeping changes need to be carefully considered. Speaking of consideration, let's use the implementation from the above solution to try something a little different:
Oh no! What's happened? true
is most certainly not equivalent to false
... at least not in Ruby. Take a look at the solution again. Can you see something we missed? If not, try running this:
Ooooohhhh, snap. See it now? Not
responds to nil?
. That's going to be a problem for us. No instance of Not
will ever be nil, by definition. That means we need to trap messages sent to Not
objects for methods defined on Object
itself so they're handled by our inversion logic