Want to stay on top? Ruby Weekly is a once-weekly e-mail newsletter covering the latest Ruby and Rails news.
     Feed Icon

Ruby’s Unary Operators and How to Define Their Functionality

By Peter Cooper / November 8, 2011

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, so this article demonstrates how.

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:

ruby-1.9.3-p0 :001 > -"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 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.

Note: You cannot redefine ! in Ruby 1.8. This code performs as-is in Ruby 1.9 only. If you remove the ! method and examples, the code otherwise works in Ruby 1.8. There's a longer 1.8 and 1.9 example later in this post.

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 for Ruby 1.8 and Ruby 1.9

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

  if RUBY_VERSION > "1.9.0"
    eval %{def !
      swapcase
    end}
  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"

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

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 months 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 I've (with assistance from Christoph Grabo) 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, of course!

Comments

  1. Dan says:

    Very informative article thanks!

    Dan

  2. roberto tomás says:

    can you provide a way to override %Q ? I'd love to have a %«comillas-enclosed string»

  3. Peter Cooper says:

    Roberto: Unfortunately not. That's a key piece of syntax (rather than an operator). However, the % string literals do support using different delimiters. The bad news is that those characters are not supported. Instead you must use "special characters" like |, ( ), { }, <>, and so forth. So the closest you could get would be:

    puts %<hello world>

  4. trans says:

    There was a patch written by Peter VanBroekhoven for custom %-literals some time ago, It would have allowed for overriding and defining new %wahtever like method, but Matz rejected it. Perhaps it could stand reintroduction for Ruby 2.0.

  5. trans says:

    I have a question. How does redefining `!` effect `a != b` ?

  6. Benoit Daloze says:

    @trans
    With a quick try in IRB:

    > c = Class.new(Struct.new(:a)) do def !; p [self, :!]; a.downcase; end; def == o; p [self, :==, o]; super; end; end
    > c.new(:A) != c.new(:B)
    [#, :==, #]
    => true

    So a != b is just !(a == b) by default.
    But != is actually a binary operator from BasicObject (in 1.9):

    "a".method :!=
    => #
    > class BasicObject; def != o; "Hello"; end; end
    > "a" != "b"
    => "Hello"

    The implementation is really simple:

    rb_obj_not_equal(VALUE obj1, VALUE obj2)
    {
        VALUE result = rb_funcall(obj1, id_eq, 1, obj2); // iq_eq is the ID of :==
        return RTEST(result) ? Qfalse : Qtrue;
    }
     rb_define_method(rb_cBasicObject, "!=", rb_obj_not_equal, 1);
  7. 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 :)

  8. Peter Cooper says:

    The real fun comes if you redefine ! on TrueClass. So say ! on TrueClass returns true. So !true == true ;-)

  9. trans says:

    Thank you. That helps.

  10. Milan Dobrota says:

    Can you define your own unary operators?

  11. Mike says:

    @Milan,

    No, you can't define your own operators, nor can you override operators.

    The "operators" that Peter is overriding are actually methods.

  12. Peter Cooper says:

    Mike: In -"something" the unary minus is an operator. Its implementation is internally represented as a method but it, itself, is an operator.

    No, you cannot create your own operators, but you can override the implementation of (many, but not all) operators, which is all we care about overriding anyway.

    To those who posted offensive "these aren't operators" comments (this doesn't apply to you, Mike, since you were civil about it!) which I deleted, send your complaints to the many sites, including the official Ruby site, and authoritative Ruby books that refer to operators as operators. Unfortunately, I haven't the time to correct your errors on a case by case basis. My post stands.

  13. @asaaki says:

    Oh, I was mentioned in the article and nobody told me!

  14. Peter Cooper says:

    Makes it more fun when you find out, no? ;-)

  15. cantbecool says:

    Awesome, Peter, thanks for the information. I'll try to incorporate custom uses for the unary operators in one my next applications.

  16. secondplanet says:

    I saw some really cool uses of this kind of thing in this library: https://github.com/youpy/scissor


    foo, bar = Scissor 'foo.mp3', Scissor 'bar.mp3'
    foo + bar > 'foobar'.mp3 # Magic!

    It lets you use operators to concatenate audio files!

Other Posts to Enjoy

Twitter Mentions