Your Program is a Special and Unique Snowflake

Developers live in a world of abstraction. Rails abstracts away the details of
web requests, providing you with a world of routes and controllers in place of
requests and responses. Ruby hides the fact that you’re constantly allocating
and deallocating memory. Ruby doesn’t have to tell the computer how to allocate
memory, though, because it’s written in C.

We can’t live without abstractions, because there’s too much detail in our world
to hold in all at once. We have to build bigger concepts out of smaller concepts
until we can fit them all in our human brains.

One of the great challenges of writing usable code is deciding what and when to
abstract. We want just enough abstraction that we can hold the problem in our
heads, but not so much that we can’t tell what’s actually going on.

One technique for keeping that balance is to use fewer abstractions by avoiding
special cases. Specializations can be easy to learn because they’re more
concrete, but we need more special cases to solve most problems because each
special case only applies to a specific situation.

The for loop introduces new syntax and keywords for a single concept. The
contents will be performed for each item in a list. Once you’ve learned how
for loops work, you know how to do one thing. You’ve gained one ability for
one keyword. That’s not a great return on your investment. If you’re going to
fit a lot of programming concepts in your head, you’ll need to do better!

The each method builds on widely-used concepts from Ruby: methods and blocks.
To truly understand each, you need a solid understanding of some more abstract
concepts. This takes longer, but you’ll be well-rewarded for your efforts:
almost every problem in Ruby is phrased in terms of objects, methods, and
blocks.

Another nice aspect of the each abstraction is that we can build it ourselves
in Ruby:

Our implementation introduces some new concepts we have to understand:
conditionals, the unless keyword, the empty? and call methods, array
globbing, recursion, and block arguments. Veteran Rubyists may not realize how
many concepts are involved in what we consider a basic building block of
application logic, but you had to learn every one of these concepts at some
point to use them effectively.

On the other hand, we can’t implement a for loop ourselves. We can’t break it
down any further, because it’s a special syntax and doesn’t build on other
concepts from Ruby.

If you can implement a new idea in terms of other ideas that are already
defined, you don’t need as many abstractions in total. How many ideas can you
eliminate from your program by building and reusing abstractions?

Our usage patterns are very similar, but the second example removes an
abstraction entirely. Now we don’t worry about arrays and nils; we only worry
about arrays.

This works because nil is a specialization of Array. An Array represents a
situation where there is an unknown number of something. A nil represents a
situation where there could either be one or zero of something.

Sometimes nil may be just the right abstraction, but there are many ways to
represent an empty case. A signed out user can be nil or an instance of a
Guest class or an unsaved instance of your normal User class. Which
introduces the best balance of low abstraction and low confusion for your
application?

Many computations can be performed using an abstraction called “folding,”
represented by the reduce (aka inject) method in Ruby:

# Using special casesclassGamedefscoreresult=0rounds.each{|round|result+=round.score}resultenddefcompetitorsresult=[]rounds.eachdo|round|round.users.eachdo|user|result<<user.emailendendresultendend# Using abstractionclassGamedefscorerounds.reduce(0){|result,round|result+round.score}enddefcompetitorsrounds.reduce([]){|result,round|result+round.users.map(&:email)}endend

In this case, there is another level of abstraction built on top of a fold:

Almost everything you use in programming is an abstraction, but the trick is to
decide how abstract you want to be. When coding try making your programs more
abstract to see if you can eliminate special cases. Is it harder to understand
without the clarity of specialization, or is it easier to follow because it uses
a single level of abstraction?