JS: Building abstractions with data
Theory: Level-based design
Consider another simple system - rational numbers and operations you can perform on them. Remember that a rational number is a number representable as a fraction a/b where a is the numerator of the fraction and b is the denominator. And b must not be zero, since division by zero is not allowed.
JS does not support rational numbers, so we'll create an abstraction for them ourselves. As usual, we will need the constructor and selectors:
Using three functions, we've defined a rational number. One function (constructor) build it from parts, and others (selectors) allow each component to be extracted. What num is from the language's perspective is irrelevant. It can be a function (not impossible), an array, or an object. You can even use strings in the internal implementation:
Although we've learned how to represent rational numbers, this abstraction itself is of little use. Abstraction becomes useful when it becomes possible to operate on it. For rational numbers, arithmetical operations are the basic ones, such as addition, subtraction, or multiplication. Multiplication of rational numbers is the simplest operation. To do this, multiply the numerators and denominators:
The most interesting part begins in the implementation process. If we assume that the real structure of a rational number looks like this: { numer: 2, denom: 3 }, then, purely technically, the solution could be this:
From the caller's perspective, everything is fine, and the abstraction is preserved. mul input is a rational number, and the output is a rational number. But inside, there is no abstraction; we treat rational numbers knowing how they are implemented. Any change in the internal implementation of rational numbers would require rewriting all operations that work with rational numbers directly, i.e., without selectors or a constructor. This code violates the single-level abstraction principle.
When developing complex systems, we use a level design approach. It consists in structuring the system using successive levels. Each level is built by combining parts considered elementary at that level. The parts built at each level act as elementary ones (primitives) at the next level.
Stratified design pervades the engineering of complex systems. For example, in computer engineering, resistors and transistors are combined (and described using a language of analog circuits) to produce parts such as and-gates and or-gates, whichform the primitives of a language for digital-circuit design. These parts are combined to build processors, bus structures, and memory systems, which are in turn combined to form computers, using languages appropriate to computer architecture. Computers are combined to form distributed systems, using languages appropriate for describing network interconnections, and so on. (c) SICP
In our example, the base level consists of types built into the language itself: numbers and objects. On top of this, we create a level for representing rational numbers: makeRational, getDenom, getNumer. Then there's a level of arithmetic operations on rational numbers: addition, subtraction, multiplication, and so on.
I should emphasize that we are talking about the level implementation itself. For example, the addition operation relies entirely on the constructor and selectors but cannot know anything about the inner workings of rational numbers themselves. On the other hand, this does not mean that functions from different levels can't appear in one place. They can, and this is normal in many cases. For example:

