Saying stuff about stuff.

Ruby’s identity method

When Ruby 2.2 added #itself I couldn’t think of anything I’d previously encountered where it would have been useful but I’ve finally used it in the wild. I had an array of integers and wanted to count their occurrences. Initially reaching for each_with_object I was hoping for something more meaningful, intention-revealing, and succinct.

[3, 1, 2, 1, 5].each_with_object(Hash.new(0)) { |i, memo| memo[i] += 1 }
# => {3=>1, 1=>2, 2=>1, 5=>1}

I remembered something about an identity method — #itself — and by using it you get the following (which is definitely more succinct):

[3, 1, 2, 1, 5].group_by(&:itself)
# => {3=>[3], 1=>[1, 1], 2=>[2], 5=>[5]}

So now I could write a method to extract pairs:

def pairs(array)
  array
    .group_by(&:itself)
    .select { |_, v| v.size == 2 }
end

pairs([3, 1, 2, 1, 5])
# => {1=>[1, 1]}

pairs([9, 1, 2, 2, 9])
# => {9=>[9, 9], 2=>[2, 2]}

In particular I wanted to check whether the array contained two groups each containing two items:

def two_pairs?(array)
  pairs(array).size == 2
end

two_pairs?([3, 1, 2, 1, 5])
# => false

two_pairs?([9, 1, 2, 2, 9])
# => true

Enumerable#tally

Since I wrote this Ruby 2.7 was released and added Enumerable#tally which behaves exactly like my initial code to count occurrences using #each_with_object:

[3, 1, 2, 1, 5].tally
# => {3=>1, 1=>2, 2=>1, 5=>1}

Pre-#itself hack

Looking around the internet it turns out that people were already getting around the lack of an identity method by calling a no-op method that returns self:

[3, 1, 2, 1, 5].group_by(&:to_i)
# => {3=>[3], 1=>[1, 1], 2=>[2], 5=>[5]}

self and #itself, what’s the difference?

To be honest although I expected it to fail I had my fingers crossed trying to use self like so:

# This doesn’t work.
[3, 1, 2, 1, 5].group_by(&:self)
# => NoMethodError (undefined method `self' for 3:Integer)

It doesn’t work but I didn’t know why. It turns out that self isn’t a method, it’s a special keyword whereas #itself is a method defined on Object (although the Ruby 2.2 release notes say it’s defined on Kernel).


Updated 2021-02-19: Added the section on Enumerable#tally.