Anti-Pattern: Iteratively Building a Collection
source link: https://thoughtbot.com/blog/iteration-as-an-anti-pattern
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Ruby comes with many fantastic Enumerable methods, but the two most
useful ones come through Smalltalk via LISP: #map
and #inject
. What
follows are some lengthy method definitions followed by rewrites that
are not only more concise but also more clear in their intentions.
Building an array
Requirement:
As a user with a PGP key I want to see the list of key ids for all my signers so I can quickly import them from the keyserver.
The initial implementation is a little lengthy and overly explicit:
def signer_key_ids
result = []
signers.each do |signer|
result << signer.key_id
end
result
end
But a simple use of #map
more clearly illuminates what this method
does:
def signer_key_ids
signers.map { |signer| signer.key_id }
end
Building an array from multiple arrays
Another requirement comes in:
As a user with a PGP key I want to see the list of all UIDs for all my signers so I can see their names and where they work.
We can write this in a structured way using #each
and #flatten
:
def signer_uids
result = []
signers.each do |signer|
result << signer.uids
end
result.flatten
end
But a #map
makes it more clear. Note the use of Symbol#to_proc
here:
def signer_uids
signers.map(&:uids).flatten
end
An #inject
combined with Array#+
removes the need to call #flatten
at the end:
def signer_uids
signers.inject([]) do |result, signer|
result + signer.uids
end
end
Though in this case using #inject
is the long way; instead try
Enumerable#flat_map
:
def signer_uids
signers.flat_map(&:uids)
end
Build a hash from an array
Another requirement comes in from above:
As a user with a PGP key I want to see a mapping of all key ids to their UIDs for each signer so I can build my own keyserver.
Well we need to build a hash, and we need to build it from each element in an array. At least, that’s one way to phrase it:
def signer_keys_and_uids
result = {}
signers.each do |signer|
result[signer.key_id] = signer.uids
end
result
end
But another way to phrase it is: given an empty hash, #inject
a hash
from key id to UIDs for each element in the array of signers:
def signer_keys_and_uids
signers.inject({}) do |result, signer|
result.merge(signer.key_id => signer.uids)
end
end
Build a Boolean from an array
One last requirement, they swear:
As a user with a PGP key I want to confirm that all my signers are signed by me so I can always feel mutually complete.
With the hash above we were dealing with another Enumerable. Here it’s a Boolean, so let’s try it the long way:
def mutually_signed?
result = true
signers.each do |signer|
result = result && signer.signed_by?(self)
end
result
end
Though, now that we’ve seen that, it looks a bit familiar:
def mutually_signed?
signers.inject(true) do |result, signer|
result && signer.signed_by?(self)
end
end
But if that’s too obtuse, we can always think of it as an array of Booleans that must all be true:
def mutually_signed?
signers.map(&:signed_by?).inject(:&)
end
As Rubyists we also know that we have other fantastic
abstractions up
our Enumerable
sleeve:
def mutually_signed?
signers.all?(&:signed_by?)
end
What’s next
To get a comfortable intuition with #map
, #inject
, and other
Enumerable
methods, I recommend going outside of Ruby for a bit. Some
amazing books on the topic of functional programming are:
If you want to read more about #inject
in Ruby, check out these
articles:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK