George Boole Returned As a Zombie and is Gnawing on My Brain

This is about that oft-neglected corner of programming, the humble boolean. Perhaps, by thinking about what’s going on in many boolean expressions, we can iron out his quickly-forking complexity. And, oh, what luck! Here’s a slab of boolean logic that I just happened to have at hand (variable names have been changed to protect the innocent):

def prithee_shall_i_do_some_stuff?
  (hash[:foo] == "1" || !hash.has_key?(:foo)) && \
  (!hash["bar"].include?("Horses") && \
    hash["baz"].blank? && \
    hash["quux"].blank? && \
    hash["buzz"].blank?)
end

I have found that stuff like this is easy for me to get wrong, hard to read, and hard to refactor. What could be done to simplifiy these cases? Consider just the first line of the above, (hash[:foo] == "1" || !hash.has_key?(:foo)). Already, there are two competing interests here. What is going on is that we are conflating value-testing with existence-testing. What we may mean with the above is:

equal to "1"  not equal to "1"
    has :foo   TRUE          FALSE
not has :foo   TRUE          TRUE

Having first to check that hash[:foo] isn’t nil before we can proceed is accidental complexity.

Aside: Joe pointed out that this is material implication:

has_key(:foo) *implies* it is equal to "1"

Continuing: I’d like to just assert the case I care about and let “the rest” be true (or maybe false, I hadn’t thought about that):

hash.true_of?(:foo) { value == "1" || key == nil }

Note: inside of the block you just use key and value to refer to the hash element that you have selected in the first argument to true_of?.

Here’s how to write it:

class Hash
  def true_of?(subject_key, &block)
    TrueOf.new(self).instance_eval(&block)
  end
end

class TrueOf
  attr_reader :value, :key

  def initialize(hash, key, &block)
    @hash = hash
    @key = key
    @value = hash[key]
  end
end

Is this useful? Devin Walters and I set out to rewrite this, and after much strife and refactoring (and the help of some IRCing) we reached a solution that we were proud of. It has me looking around for other cases where there are complicated booleans that are better recast as known logical functions. In this case it was useful in capturing the idea of A user is on the first page if they have a params[:page_num] == "1" or if it doesn’t exist (undefined page number).

I got inspiration from Raganwald’s article on anaphora

And here are some other (perhaps relevant) links that will get you thinking about alternate ways to simplify expressions:


Category: Development
Tags: Tutorial