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.

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