Ruby Weekly is a weekly newsletter covering the latest Ruby and Rails news.

Ruby’s Unary Operators and How to Redefine Their Functionality

By Peter Cooper / October 16, 2014

In math, a unary operation is an operation with a single input. In Ruby, a unary operator is an operator which only takes a single 'argument' in the form of a receiver. For example, the - on -5 or ! on !true.

In contrast, a binary operator, such as in 2 + 3, deals with two arguments. Here, 2 and 3 (which become one receiver and one argument in a method call to +).

Ruby only has a handful of unary operators, and while it's common to redefine binary operators like + or [] to give your objects some added syntactic sugar, unary operators are less commonly redefined. In my experience, many Rubyists aren't aware that unary operators can be redefined and.. technically you can't "redefine an operator" but Ruby's operators frequently use specially named methods behind the scenes, and as you'll know.. redefining a method is easy in Ruby!

A Quick Example with -@

Let's ease into things with the - unary operator. The - unary operator is not the same thing as the - binary operator (where a binary operator has two operants). By default, the - unary operator is used as notation for a negative number, as in -25, whereas the - binary operator performs subtraction, as in 50 - 25. While they look similar, these are different concepts, different operators, and resolve to different methods in Ruby.

Using the - unary operator on a string in irb:

> -"this is a test"
NoMethodError: undefined method `-@' for "this is a test":String

The String class doesn't have unary - defined but irb gives us a clue on where to go. Due to the conflict between the unary and binary versions of -, the unary version's method has a suffix of @. This helps us come up with a solution:

str = "This is my STRING!"

def str.-@
  downcase
end

p str     # => "This is my STRING!"
p -str    # => "this is my string!"

We've defined the unary - operator by defining its associated -@ method to translate its receiving object to lower case.

Some Other Operators: +@, ~, ! (and not)

Let's try a larger example where we subclass String and add our own versions of several other easily overridden unary operators:

class MagicString < String
  def +@
    upcase
  end

  def -@
    downcase
  end

  def !
    swapcase
  end

  def ~
    # Do a ROT13 transformation - http://en.wikipedia.org/wiki/ROT13
    tr 'A-Za-z', 'N-ZA-Mn-za-m'
  end
end

str = MagicString.new("This is my string!")
p +str         # => "THIS IS MY STRING!"
p !str         # => "tHIS IS MY STRING!"
p (not str)    # => "tHIS IS MY STRING!"
p ~str         # => "Guvf vf zl fgevat!"
p +~str        # => "GUVF VF ZL FGEVAT!"
p !(~str)      # => "gUVF VF ZL FGEVAT!"

This time we've not only redefined -/-@, but the + unary operator (using the +@ method), ! and not (using the ! method), and ~.

I'm not going to explain the example in full because it's as simple as I could get it while still being more illustrative than reams of text. Note what operation each unary operator is performing and see how that relates to what is called and what results in the output.

Special Cases: & and *

& and * are also unary operators in Ruby, but they're special cases, bordering on 'mysterious syntax magic.' What do they do?

& and to_proc

Reg Braithwaite's The unary ampersand in Ruby post gives a great explanation of &, but in short & can turn objects into procs/blocks by calling the to_proc method upon the object. For example:

p ['hello', 'world'].map(&:reverse)  # => ["olleh", "dlrow"]

Enumerable#map usually takes a block instead of an argument, but & calls Symbol#to_proc and generates a special proc object for the reverse method. This proc becomes the block for the map and thereby reverses the strings in the array.

You could, therefore, 'override' the & unary operator (not to be confused by the equivalent binary operator!) by defining to_proc on an object, with the only restriction being that you must return a Proc object for things to behave. You'll see an example of this later on.

* and splatting

There's a lot of magic to splatting but in short, * can be considered to be a unary operator that will 'explode' an array or an object that implements to_a and returns an array.

To override the unary * (and not the binary * - as in 20 * 32), then, you can define a to_a method and return an array. The array you return, however, will face further consequences thanks to *'s typical behavior!

A Full Example

We've reached the end of our quick tour through Ruby's unary operators, so I wanted to provide an example that shows how to override (or partially override) them that should stand as its own documentation:

class MagicString < String
  def +@
    upcase
  end

  def -@
    downcase
  end

  def ~
    # Do a ROT13 transformation - http://en.wikipedia.org/wiki/ROT13
    tr 'A-Za-z', 'N-ZA-Mn-za-m'
  end

  def to_proc
    Proc.new { self }
  end

  def to_a
    [self.reverse]
  end

 def !
   swapcase
 end
end

str = MagicString.new("This is my string!")
p +str                   # => "THIS IS MY STRING!"
p ~str                   # => "Guvf vf zl fgevat!"
p +~str                  # => "GUVF VF ZL FGEVAT!"
p %w{a b}.map &str       # => ["This is my string!", "This is my string!"]
p *str                   # => "!gnirts ym si sihT"

p !str                   # => "tHIS IS MY STRING!"
p (not str)              # => "tHIS IS MY STRING!"
p !(~str)                # => "gUVF VF ZL FGEVAT!"

It's almost a cheat sheet of unary operators :-)

A Further Example: The TestRocket

TestRocket is a tiny testing library I built for fun a few years ago. It leans heavily on unary operators. For example, you can write tests like this:

+-> { Die.new(2) }
--> { raise }
+-> { 2 + 2 == 4 }

# These two tests will deliberately fail
+-> { raise }
--> { true }

# A 'pending' test
~-> { "this is a pending test" }

# A description
!-> { "use this for descriptive output and to separate your test parts" }

The -> { } sections are just Ruby 1.9+ style 'stabby lambdas' but, with assistance from Christoph Grabo, I added unary methods to them so that you can prefix +, -, ~, or ! to get different behaviors.

Hopefully you can come up with some more useful application for unary methods on your own objects ;-)

Comments

  1. zipizap says:

    It seems dangerous to override the "!" operator, because when used in conditions (if, until, ...) the condition may not evaluate as expected...

    For example (in Ruby 1.9):

    >> str = "hey"
    "hey"
    >> puts "if condition valued to true" if (str)      #(str) condition evaluated to "true"
    if condition valued to true
    nil
    >> puts "if condition valued to true" if (!str)     #(str) condition evaluated to "false"
    nil
    
    ### untill here, everything was normall
    ### now we overload the ! operator of "str" , and see how it impacts the if conditions-evaluation:
    
    >> def str.!; swapcase; end
    nil
    
    >> puts "if condition valued to true" if (str)        #(str) condition evaluated to "true", as before
    if condition valued to true
    nil
    >> puts "if condition valued to true" if (!str)       #(str) condition evaluated to **"true"** ----> because of the ! overloading
    if condition valued to true
    nil

    It seems a very practical option to overload unary operators, but I should not use it for the "!", as it may give unexpected results inside conditions

    Great post :)

Other Posts to Enjoy

Twitter Mentions