In Progress
Unit 1, Lesson 21
In Progress

Exclusive Or

Video transcript & code

Let's say we have a method which searches a string of text for a special merge variable pattern. Wherever it finds the pattern, it replaces it with a given value.

# Replace variables of the form {foo}
def replace_var(text, var_name, value=nil)
  text.gsub!(/\{#{var_name}\}/) { value || yield }
end

The replacement value can be specified as an argument.

replace_var("Hello, {name}", 'name', 'Avdi')

…or, the value can be specified using a block, in which case the return value of the block is used as the replacement value.

text = "my {noun} is full of {noun}s"
replace_var(text, 'noun') { 
  %w[hovercraft eel macaroon dreidel cabana parrot].sample 
}

The preconditions for input to this method are: the caller must always supply either a replacement value or a block. But it also should never supply both, since only one can be used.

Assuming this method will be used by other people besides ourselves, it would be nice if it could explicitly check that it was being called correctly, and raise an exception if not.

Validating that only one form of replacement text is supplied is a perfect fit for Ruby's exclusive-or, or caret operator. Let's do a quick refresher on how exclusive-or works.

Given two falsey values, it returns false:

false ^ nil                     # => false

Given two truthy values, it also returns false:

true ^ 42                       # => false

But given a truthy and a falsey value, it returns true:

true ^ false                  # => true

Note that the caret operator isn't defined for all Ruby classes, so if the first operand might be something other than a true, false, or nil value, we need to convert it into a boolean first. The easiest way to do that is with the double-bang:

!!nil                           # => false
!!"spam"                        # => true
!!"spam" ^ false                # => true

With this knowledge fresh in our minds, we can add an assertion to our #replace_var method which checks that one and only one type of replacement text was given.

When we test it out, we can see that it raises an exception if no replacement is supplied, as well as if both forms are supplied.

def replace_var(text, var_name, value=nil)
  unless block_given? ^ value
    raise ArgumentError, 
          "Either value or block must be given, but not both"
  end
  # ...
end

text = "my {noun} is full of {noun}s"

replace_var(text, 'noun') rescue $! 
# => #<ArgumentError: Either value or block must be given, but not both>

replace_var(text, 'noun', 'world'){'Avdi'} rescue $!
# => #<ArgumentError: Either value or block must be given, but not both>

And there you have it, the exclusive-or operator. A handy way to check if one or the other, but not both, of two options is present.

Happy hacking!

[su_note note_color="#eeeeee" radius="0"]Editor's note: The Integer exclusive-or in Ruby is significantly different; it applies this xor algorithm bit-by-bit across both integers.[/su_note]

Responses