Unlocking The Power Of Case Equality: Proc#===

Update: The concepts we proposed in this article have been implemented in Ruby 1.9.

The ‘===’ operator is known as the case equality operator and is used when testing cases in case statements. Many classes take advantage of this by implementing their own versions of ‘===’ to enable developers to write neat, compact code. It’s use could potentially be expanded even further. For instance in the following case statement:

1 case number 
2   when 1: puts "One"
3   when 2..10: puts "Between two and ten"
4   when String: puts "Not a number"
5 end

for each ‘when’ statement the ‘===’ operator is called on the when argument (1,2..10,String) and the case argument (number) is used as the RHS of the comparison. For line 2 the comparison done will be ‘1 === number’.

For most classes ‘===’ is just an alias to ‘==’ however some classes redefine ‘===’ to allow for better functionality in case statements. Range redefines ‘1..10 === 5’ to ‘1..10.include?(5)’ and Regex redefines ‘/abcdef/ === “abcdef”’ to ‘/abcdef/ =~ “abcdef”’ (the Regexp matching operator). Consider also the fact that Range, Regexp and Class can match multiple parameter values against the same object with ‘===’. Any integer from 1 to 10 will cause ‘1..10 === num’ to evaluate to true. Similarily “aaa”, “baaab” and “caaac” all satisfy the RHS of ‘/aaa/ === str’ and 1,2 and 3 will satisfy the RHS of ‘Integer === number’.

But what about Proc? In Ruby a Proc is a block of executable code, an anonymous function which can be passed around as if it were data. Like ‘===’, a Proc can map a number of arguments to either a true or false value (although in Ruby it could have a number of outputs but only ‘nil’ and ‘false’ will be considered false). So there is justification that Proc should have an ‘===’ operator.

For instance given two Proc objects as follows:

1 multiple_of_3 = lambda do |number|
2   number.modulo(3).zero?
3 end
4 multiple_of_7 = lambda do |number|
5   number.modulo(7).zero?
6 end

we want

1 multiple_of_3 === 14 # => false 
2 multiple_of_7 === 14 # => true 

so that

1 case 14 
2   when multiple_of_3: puts "Multiple of 3"
3   when multiple_of_7: puts "Multiple of 7"
4 end

will satisfy the expression on line 3 and output “Multiple of 7” to the screen.

Implementing this in Ruby is easy, we simply take the parameter to === and pass it as the parameter to Proc#call:

1 class Proc
2   def ===( *parameters )
3     self.call( *parameters )
4   end
5 end

Using the splat operator allows for Proc to use ‘===’ with multiple parameters, you merely specify the parameters in an array. So given the following Proc:

1 multiples_of_5 = Proc.new{|*parameters| parameters.all?{|i| i.modulo(5).zero?}}

then line 2 will be satisfied

1 case [5,10,15]
2   when multiples_of_3: puts "Multuples of 5"
3   when multuples_of_5: puts "Multiples of 7"
4 end

Now creating Procs for each case is a bit tiresome and doesn’t seem to save any keystrokes but using Ruby’s ability to dynamically create Procs we can define a function to do it for us:

1 def multiple_of(factor)
2   Proc.new{|product| product.modulo(factor).zero?}
3 end
5 case number
6   when multiple_of(3): puts "Multiple of 3"
7   when multiple_of(7): puts "Multuple of 7"
8 end

Put the ‘multiple_of’ function in a utility library and your resultant code looks pretty neat and tidy.

In our next posting in the “Unlocking The Power Of Case Equality” series of posts we’ll talk about how Enumerable could take advantage of case equality.

Farrel Lifson is a lead developer at Aimred.

About Aimred

Aimred is a specialist Ruby and Ruby on Rails development house and consultancy based in Cape Town, South Africa.

We provide Ruby and Ruby on Rails development, consulting and training services to businesses and organisations of all sizes. If you want to find out how we can help you, contact us at info@aimred.com.

Recent Posts

Yearly Archives