Aimred Developer Blog August 2008 Archive
Unlocking The Power Of Case Equality: Modifying Enumerable To Use Case Equality
In the first post in our series ‘Unlocking the Power of Casey Equality’ we talked about adding case equality to Procs. In this post we’ll talk about how we can extend Enumerable to take advantage of the case equality operator ‘===’ to write even cleaner and more elegant code than using the regular block syntax.
Enumerable is one of Ruby’s more powerful modules. By simply implementing ‘each’, which successively yields each element in a collection, and including the Enumerable module, your collection gains the methods in Enumerable which include workhorses like ‘map’, ‘inject’ and ‘to_a’. Builtin classes like Array and Hash have Enumerable included automatically.
Of the included methods let’s look at one particular method: ‘grep’. ‘grep’ takes a a single argument called the pattern, and that pattern only needs to respond to ‘===’ (see where we’re going here?). Each element is compared to the pattern with ‘===’ and if the comparison is successful the element is included in the results array. The following code shows how a Range or Class, which both implement ===, can be used with ‘grep’.
1 [1,2,3,4,5,6,7].grep( 3..5 ) # => [3,4,5] 2 ["1",1,"2",2,"3",3].grep( String ) # => ["1","2","3"]
Now compare line 2 to the following code snippet using block syntax, which checks if every element in an array is a String:
1 ["1",1,"2",2,"3",3].all? { |e| e.instance_of? String } # => false
If we could apply the ‘grep’-style of passing objects that respond to ‘===’ to a function like ‘all?’ we could have code that looks like:
1 ["1",1,"2",2,"3",3].all? String # => false
Which looks a lot cleaner and nicer than using a block and having to explicitly call ‘instance_of?’. Reading the snippet above it’s functionality is still quite apparent: ’Are all the elements of the array strings?".
When a pattern and a block are both present they are both applied to the element and their results are combined with &&.
1 [1,2,3,4].all?( Integer ){ |i| i < 5 } # => true
Implementing the modifications would seem to require minimal changes to existing code. For instance ‘all?’ is implemented in C in the Ruby API but the modified code for the Rubinius VM would be:
1 module Enumerable 2 def all?( pattern = Object, &prc ) 3 prc = Proc.new{ |o| o } unless block_given? 4 each{ |o| return false unless ( pattern === o ) && prc.call( o )} 5 true 6 end 7 end
We set the default value of the pattern to Object because every instantiated object has the Object class in it’s ancestry and so ‘object.kind_of?( Object ) is true and therefore ’Object === object’ will always return true.
The following methods in Enumerable would be candidates to be modified to take a pattern argument along with a block:
- all?
- any?
- count
- detect/find
- find_index
- none?
- one?
- find_all
Some example usage:
1 ["1",2,"3",4,"5"].count String # => 3 2 ["aaa","aab","aac"].detect /ac/ # => "aac" 3 [100,50,25].one? 20..30 # => true
I don’t think these proposed changes would ever get into the Ruby API (at least not in the near future), but it does go to show there is still room to make things even more concise and elegant.
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 4 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.