In a recent blog post I described the principles behind Bling’s design. The primary principle is preferring programmable over fixed abstractions. But I feel that my definition of a programmable abstraction is so far unsatisfactory and undeveloped. To converge on a better definition, let’s first start at the beginning and discuss abstraction.
The process of abstraction (verb) is that of hiding details so that one can focus on fewer concepts at a time. An abstraction (noun) then is a hider of details. A programming language provides various primitives for constructing custom abstractions, such as procedures, classes, functions, properties, or rules—let’s refer to these as abstraction primitives to distinguish them from computation primitives, which are used to build up computations that use or are hidden by abstractions. Using abstraction primitives, a library then define and implement an API as a set of abstractions that permit access to some kind of service. Of course, real life is often more complex: one or more languages can be involved in the definition of a library (e.g., WPF and C#, Visual Basic, XAML, and HLSL), while libraries will often be built from or to interoperate with abstractions from other libraries.
Libraries are increasingly defined as frameworks that provide heavy large-grained abstractions for assembling systems in specific domains; for example consider a web framework (Ruby on Rails) or an IDE framework (Eclipse). Programmers customize a framework’s abstractions from the top-down according to their needs and assemble them together according to a rigid architecture. The trade off of using a frameworks includes the learning curve of the framework’s abstractions and architecture, as well as a sacrifice in flexibility where the framework becomes difficult or impossible to program outside of its intended purpose.
I am going to refer to framework abstractions as fixed abstractions because they are analogous to abstractions used to program a GPU through a fixed-function graphics pipeline as opposed to the abstractions used to program a GPU through a programmable pipeline. In the past, GPUs encoded in hardware only various fixed functions to manipulate vertices, light pixels, and so on. To program the GPU, the programmer manipulated various abstractions that represent fixed GPU functions through an API such as DirectX or OpenGL. As with a framework, these fixed-function APIs allowed for some customization of abstractions from the top-down, come with rigid architectures, and sacrifice flexibility in what they can express.
For the reason of flexibility alone, fixed-function pipelines are being rapidly replaced programmable pipelines that have the ability to express arbitrary custom effects through small programs known as shaders, which run directly on the GPU. The programmable pipeline completely supersedes the fixed-function pipeline. For example, whereas a specific kind of light (e.g., spotlight) and material (e.g., specular) are fixed abstractions that would be customized and installed in a fixed-function pipeline, now it is merely sufficient to take the position of the vertex or pixel being shaded and return a color! At first glance, such an API seems a lot harder to use in simple cases where well non-custom understood lighting is being used, but then such cases can rely on reusable procedures that package up the lighting math. Beyond dramatically increased flexibility, the programmable pipeline approach also has a better learning curve as their are only a few lightweight abstractions to learn with virtually no architecture. For an excellent write up what the move from fixed-function to programmable pipelines means, see Tim Sweeney’s Ars interview on the subject.
What do programmable pipelines and shaders have to do with software libraries and frameworks? They hint at an alternative to frameworks: rather design a library with many large-grained abstractions (fixed functions) that programmers customize down to what they want, instead we could design a library with a few small-grained versatile abstractions (shaders) that programmers then build up to what they want. We refer to the latter as programmable abstractions, which are smaller and more generally used than fixed abstractions. Large-grained reuse then comes from assembling the programmable abstractions together and packaging them up through language-level abstraction primitives (e.g., procedures). Programmable abstractions are not a new concept: they underpin many libraries, especially those built in functional programming languages. Additionally, programmable abstractions can be promoted to language-level abstractions with domain-specific languages (e.g., pipes in shell script), where the key to defining a good DSL is to base it on versatile programmable abstractions rather than fixed abstractions. I feel that a term for such abstractions is needed because of the confusion surrounding languages, frameworks, libraries, DSLs, and something in between (highly versatile libraries that are not quite DSLs).
I’m not exactly sure what the threshold between a programmable abstraction and a fixed abstraction is, it might not be very clear cut. Also, the benefits of programmable abstractions are negated if excessive amounts of boilerplate are needed to use them, languages can go a long way in eliminating this boilerplate and making programmable abstractions more feasible (e.g., see express tree lifting in LINQ). To be continued, in the next part I’ll provide some concrete examples of differences between fixed and programmable abstractions.