
At work I sometimes have occasion to write number-crunching C code, so I’m used to the & and | operators standing for bitwise AND and OR. I’m also a reformed PHP hacker (aren’t we all?), so I’m used to === meaning “equality without type coercion”. These definitions are only occasionally true in Ruby, because meaning depends on context: one thing I’ve come to appreciate is how many Ruby “operators” are just methods, and so they often have different implementations depending on the object receiving the method call.
Thing is, you gotta learn how to operate, Ruby-style.
Unusual operator expressions
&— In an integer context (FixnumandBignum), this is indeed a bitwiseAND. But in a logical context, it’s just like&&(logicalAND), but without short-circuiting when the receiver (that is, the left operand) evaluates tofalseornil. Both operands are always evaluated. This is because&is implemented as an instance method for the singletonsTrueClass,FalseClass, andNilClass, and arguments to a method are evaluated when it’s called. (&&, on the other hand, is not a method. Like all the non-method operators and other punctuation, it’s picked up by the parser and handled in C.&&is logically implemented in Ruby’s expression-evaluation function.)Why would you want a logical
ANDwithout short-circuiting? Got me. In fact, I have a theory that the lack of short-circuiting is just a side-effect of the method-based implementation, and that the real reason for this operator to be defined for logical evaluation is to have a stricterANDthat doesn’t allow duck typing in certain scenarios, since the receiver has to betrue,false, ornil:1 2
5 & true # => TypeError: can't convert true into Integer 5 && true # => true
I pretty much just made that up, though, and I’m all ears if you have other ideas. It strikes me that all the obvious reasons to not want short-circuiting don’t cut it in this situation: wanting to ensure that the side-effect of the right operand happens seems very un-Ruby (say, opening a file or inline assignment—why force it into a logical expression?), and wanting to have arbitrary return values for the expression doesn’t actually work because
&expressions always returntrueorfalse. It works for&&, though:1 2 3 4
# returns false or integer representation of privileges for user def administrative_privilege_level is_admin? && privileges.to_i end
|— Same as&, but forOR.^— ExclusiveOR: evaluates to true if exactly one of the operands is non-falseand non-nil.
Since these are just methods, you can call them using the normal syntax. Take ==, another operator-as-method: evaluating Class == Class is the same as calling Class.==(Class), that is, passing the argument Class to the instance method == on the receiver, Class, which itself is an instance of type… Class. Amen.
A few more
===— Used for checking, um, equaly-ness incaseexpressions.===is a synonym of==for many objects, but some will override===to create coolcaseidioms. For example,Range#===evaluates to true if the argument is a member of the range, so you can do things like this:1 2 3 4 5 6 7
def election_table last_initial = @last_name[0].chr.downcase case last_initial when 'a'..'m' then 1 when 'n'..'z' then 2 end end
Note that
===is called on thewhenexpressions, passing thecaseobject as the argument. This is backwards from what you might assume at first glance, but it’s actually much more useful this way.Take
Module#===: it evaluates to true if the operand is an instance of the module or one of its descendants—essentially a backwardsis_a?, something likeis_class_of?. So you could use it in acasestatement to classify objects by class. (You should have a good reason to do this, though. The benefits of duck-typing come from letting go of this explicit type-checking style.) Another isRegexp, where===evaluates to true when the operand matches. So we can classify strings by regular expression.Regexp:~— a unary operator that is just like the more-common=~, except it always matches against$_. (The Pickaxe says:$_, aString, is “the last line read byKernel#getsorKernel#readline. Many string-related functions in theKernelmodule operate on$_by default. The variable is local to the current scope.”) Anyway, you can do this:1 2
$_ = 'rebarbative barbarism' ~ /barb/ # => 2
This leaves a bad taste in my mouth, though, like something shiny you find inside a mollusk. Add a few more layers of nacre, and skip the punctuation entirely using a
Regexliteral as a condition:1 2
gets # reads until \n and stores input in $_ return if /q(uit)?/ # operates on $_ magically
But that’s even worse. It’s deprecated now and you’ll get a warning if you try it. In my opinion, the only implicit receiver you should roll with is
self.String:%— Used forsprintfing with a single argument orArrayof arguments, like this:1 2
"%04x" % 61453 # => "f00d" "%s%.2f" % ['$', 821.3333] # => "$821.33"
and,or,not— Not redefinable, but worth mentioning: these function identically to&&,||, and!, but with super-low precedence.nothas the next-higher precedence fromandandor, which for some reason share the same precedence (&&beats out||usually). You can get into trouble with these:1 2 3 4 5 6
b = 3 a = b or 5 # => 3, and a is set to 3 b = false a = b || 5 # => 5, and a is set to 5 a = b or 5 # => 5, but a is set to false (!)
That happened because Ruby evaluated the expression as
(a = b) or 5, since=has a higher precedence thanor. I have to guess that this is just another feature imported from Perl that isn’t as idiomatically useful in Ruby.
Rolling your own
Now, just because many Ruby operators are implemented as methods (and can thus be overridden) doesn’t mean you can define new operators using whatever characters you want. The ability to call methods using operator syntax is hardcoded into the parser for the operators that are already defined (they’re tokens in the parser), so receiver operator argument and the unary version operator receiver won’t work. You can’t even use receiver.operator or receiver::operator, because the parser only recognizes that syntax with a name that follows the method naming conventions: a lowercase letter or underscore followed by upper- or lower-case letters, digits, or underscores, and optionally ending in !, ?, or =.
So, you can go and define all the new operators you want, but you’ll be using send to call them, since the parser won’t recognize the syntax.
1 2 3 4 5 6 7 8 9 10 |
class Hash define_method(:"<--") do inspect end end gadget = {:watch => :laser, :hat => :helicopter} gadget <-- # => parse error gadget.<-- # => parse error gadget.send(:"<--") # => "{:watch=>:laser, :hat=>:helicopter}" |
Not much point to that. However, feel free to redefine the existing operator methods in your classes. Everything will just work like you’d expect, and your objects will be good Ruby citizens.
Update: check out Jay Phillips’ Superators gem, an interesting hack to add certain new operators to Ruby DSLs by overriding the existing binary and unary operators in sequence.
References and further reading
- Table 18.4 from Programming Ruby — a nice summary of which operators are implemented as methods, sorted by precedence
- How to Duck Type? — A very readable paper on approaches to duck-typing in Ruby
- Logical operators in Perl and Ruby — All about
and,or, andnot - Input/output variable listing from Programming Ruby —
$_and friends

Subscribe to