Ross Esmond

Code, Prose, and Mathematics.

portrait of myself, Ross Esmond
Written — Last Updated

Abstraction

Abstraction is the removal of details, possibly alongside a reformulation of a process or idea to a different model of thinking. This abstraction may even allow for more details to be provided, such that the additional specificity may be used to adjust mechanical details later. There are two counterparts to an abstraction—the higher-level and lower-level code. An abstraction answers “what” the counterparts are discussing, be it a process or object. The lower-level code then answers “how” the abstraction will be implemented. A process will contain the precise steps which must be performed to complete the process, and an object will contain the precise data which must be retained to describe the object. Finally, the higher-level code answers “why” the abstraction is being utilized. A process must be triggered by certain events, and its results must be used for some purpose. Likewise, an object must be instantiated to house some data, which must be accessed later for some reason. An abstraction asks for the intention of the higher-level code in order to inform the implementation details.

Cellphones use to have a single volume setting. You could either have a loud or quiet phone, which affected all functions. There was no way to control alarm volume separately from ringer volume. Android and iOS phones today have independent volume settings for each of these functions, improving the users agency over their phone, but this adds additional complexity for applications. Now, an alarm app needs to convey to the android OS what type of noise its making whenever is requests that the phone make said noise. The phones audio facilities, which are still an abstraction, has added details which did not exist before to inform the details of how it should produce such a sound. When an app requests a sound, it generally understands why it’s doing so—because the user set an alarm—but the OS has no way of inferring that information without being told. Conversely, the OS knows how to produce a sound, but since different phones might have wildly different speaker setups, the application would have no way of deducing how to produce a sound without the operating system’s help.

A proper abstraction requests just enough information about the intention of the higher-level code to inform the details of the implementation. A “leaky” abstraction—one which exposes lower-level details to the higher-level code—often is not requesting enough information on why the abstraction is being invoked.

Forms of abstraction

Functions, objects, and data structures may all be abstractions, though the details that they hide are different. A function hides the steps taken to achieve a desired outcome or to compute a desired value. If a function is consequential (it causes a side-effect) then it hides the mechanical details of how to achieve the result. If the function is computational (it derives a value) then it hides the details of how to compute or attain that value.

An object is a collection of functions whose implementation details are hidden. In this way, it may be seen as a collection of individual function abstractions, but it has one additional ability. An object can encapsulate data which is operated upon by those functions. An object may then hide the data to which the functions read and write in order to affect, apply, and derive outcomes.

Data structures, unlike objects, expose their data and do no provide functions for themselves. Since the details of a data structure are exposed by definition, they are rarely associated with abstractions, but a data structure may still be an abstraction of anoter, more detailed data structure. If the phone number string "763-555-3503" is parsed into the simpler "7635553503", the second string is an abstraction of the first, as it removes the details of the formatting of the digits. Conversely, if the number "7635553503" is parsed into the data structure

{
  countryCode: "+1"
  areaCode: "763",
  number: "5553503"
}

the second data structure is still an abstraction of the first. In this case, the data structure has added detailed, rather than removed them, but it has hidden the details of how to attain the country code or area code from the string, abstracting that process. In a sense, a data structure abstraction is the result of applying a computation function to a prior, less abstract data structure.

Abstractions for flexibility

Abstractions serve more purposes than just simplifying the code, of couse. Abstractions may also be used to make software more flexible. In this case, the abstraction hides details so that they may be changed in one location rather than throughout the codebase.

The first company I ever worked for used custom string id’s to track patient data in a hospital system. They constructed the string using information about the record, like patient data and the time when the record was created. These id’s were guarenteed to be unique, but only inside the hospital, which created an issue when a conglomerate of hospitals requested that their records be consolidated.

The company then needed to update the id’s to include the hospital where the record originated, such that they would be globally unique, and the cost to do so supposedly numbered in the millions of dollars. This issue could have been avoided if the construction and use of the id’s was abstracted behind an Object or a Lens. If the entire codebase called into a getId function, the cost to implement the new id would have been thousands of times less.