||= [] idiom

R

Robert Klemme

2008/2/22 said:
Often it can make sense to factor the ||= portion into a separate
method:

def category_for(tag)
@categories[tag] ||= []
end

# ...

category_for(resource.tag) << resource

What's the real-world application of this?


There are two reasons to do it:

1. If you're doing this in non-trivial code, you're likely to end up
with multiple @categories[something] ||= [] scattered around; it
only makes sense to factor them into a single method at that point.
This is an advantage for maintainability.

2. "category_for" has a more direct meaning in the problem domain:
category_for(tag) will simply give you the category array for a
particular tag, without needing to expose on the spot the details
of how that category array is obtained. This is an advantage for
readability.

Also, regarding maintainability again, let's say that you decide to
start using objects of class Category instead of arrays; assuming
there is a Category#<< for adding a resource to a category, you can
simply change this:


def category_for(tag)
@categories[tag] ||= []
end


to this:

def category_for(tag)
@categories[tag] ||= Category.new
end

It's the DRY principle: Don't Repeat Yourself.

If there is repetition in your code, it is harder to read (because
you have to mentally filter out the repeated parts to see the
differences -- which are the important part), and you make more work
for yourself (because you must then edit many different places to
effect a single change, and are more likely to make mistakes).

But all these points you mention do also apply to the Hash based
approach. You can even use the same wording, if you like:

@category_for = Hash.new {|h, tag| h[tag] = []}

I was expecting reasoning that would highlight advantages of using a
method here. The only thing that comes to mind is that if you want to
exchange the implementation altogether, then a method based approach
is better. But in that case you'd probably do

def add_to_category(tag, item)
(@cat[tag] ||= []) << item
end

Because that abstracts away category creation *and* adding to the
structure. On the flipside, for small scripts that Hash based
approach has the advantage of being shorter (because no method is
needed).

Kind regards

robert
 
M

MenTaLguY

But all these points you mention do also apply to the Hash based
approach. You can even use the same wording, if you like:

@category_for = Hash.new {|h, tag| h[tag] = []}

Yes. I was thinking in terms of why to do the refactoring at all,
using my example refactoring as a point of reference, rather than
weighing the pros and cons of different refactorings of the original
code.
I was expecting reasoning that would highlight advantages of using a
method here. The only thing that comes to mind is that if you want to
exchange the implementation altogether, then a method based approach
is better. But in that case you'd probably do

def add_to_category(tag, item)
(@cat[tag] ||= []) << item
end

Because that abstracts away category creation *and* adding to the
structure.

This is a good point; I would prefer add_to_category to just category_for.
On the flipside, for small scripts that Hash based approach has the
advantage of being shorter (because no method is needed).

True, though shorter isn't necessarily clearer. One of the disadvantages
of using default blocks for hashes is the additional cognitive burden when
reading the code: you must keep in the back of your mind that that, "this
hash, it is a hash unlike any other." I don't think that's a significant
issue for small scripts, but it is why I tend to avoid the use of default
blocks in larger scripts and libraries.

-mental
 
R

Robert Klemme

But all these points you mention do also apply to the Hash based
approach. You can even use the same wording, if you like:

@category_for = Hash.new {|h, tag| h[tag] = []}

Yes. I was thinking in terms of why to do the refactoring at all,
using my example refactoring as a point of reference, rather than
weighing the pros and cons of different refactorings of the original
code.
I was expecting reasoning that would highlight advantages of using a
method here. The only thing that comes to mind is that if you want to
exchange the implementation altogether, then a method based approach
is better. But in that case you'd probably do

def add_to_category(tag, item)
(@cat[tag] ||= []) << item
end

Because that abstracts away category creation *and* adding to the
structure.

This is a good point; I would prefer add_to_category to just category_for.
On the flipside, for small scripts that Hash based approach has the
advantage of being shorter (because no method is needed).

True, though shorter isn't necessarily clearer. One of the disadvantages
of using default blocks for hashes is the additional cognitive burden when
reading the code: you must keep in the back of your mind that that, "this
hash, it is a hash unlike any other." I don't think that's a significant
issue for small scripts, but it is why I tend to avoid the use of default
blocks in larger scripts and libraries.

All good and valid points! Thank you for this discussion!

Kind regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top