Since programming languages initially only used lines of code executed sequentially, and it evolved into including functions which were one of the first levels of abstraction, and then classes and objects were created to abstract it even further; what is the next level of abstraction?
What’s even more abstract than classes or is there any yet?
10
I think you have some misconceptions about the history of computing.
The first abstraction (in 1936) was, in fact, Alonzo Church’s Lambda Calculus, which is the foundation for the concept of high-order functions and all of the functional languages that followed. It directly inspired Lisp (the second oldest high-level programming language, created in 1959), which in turn inspired everything from ML to Haskell and Clojure.
The second abstraction was procedural programming. It came out of the von Neumann computer architectures where sequential programs were written, one instruction at a time. FORTRAN (the oldest high-level programming language, 1958) was the first high-level language to come out of the procedural paradigm.
The third abstraction was probably actually declarative programming, first exemplified by Absys (1967), and then later Prolog (1972). It is the foundation of logic programming, where expressions are evaluated by matching a series of declarations or rules, rather than executing a series of instructions.
The fourth abstraction then was object-oriented programming, which made its first appearance in Lisp programs in the 60’s, but was later exemplified by Smalltalk in 1972. (Though there seems to be some debate as to whether the message-passing style of Smalltalk is the One True object-oriented abstraction. I’m not going to touch that.)
All other abstractions, especially on the traditional von Neumann computer architecture, are variations on those four themes. I’m not convinced that there is another abstraction beyond those four that is not merely a variation or a combination of them.
But an abstraction is, in essence, merely a way to model and describe an algorithm. You can describe algorithms as a series of discrete steps, as a set of rules which must be obeyed, as a set of mathematical functions, or as interacting objects. It’s very hard to conceive of any other way to describe or model algorithms, and even if there is, I’m not convinced of its utility.
There is, however, the quantum computing model. In quantum computing, new abstractions are necessary to model quantum algorithms. Being a neophyte in this area, I can’t comment on it.
12
For many, the purest form of code abstraction in the current era of binary programming is the “higher-order function”. Basically, the function itself is treated as data, and functions of functions are defined, much as you would see them in mathematical equations with operators defining the result of their operands and a predetermined order of operations defining the “nesting” of these operations. Math has very very few “imperative commands” in its structure; the two examples I can think of are “let x have some value or be any value conforming to some constraint”, and “piecewise functions” in which the input determines the expression needed to produce the output. These constructs are easily representable as their own functions; the “function” x always returns 1, and “overloads” of functions are defined in terms of what is passed to them (which unlike object-oriented overloads can be defined based on values input) allowing “piecewise” evaluation of a named group of functions, even in terms of themselves. As such, the program does away with the notion of imperatives at the low level, and instead focuses on “evaluating itself” given input data.
These higher-order functions form the backbone of “functional languages”; what a program does is defined in terms of “pure functions” (one or more inputs, one or more outputs, no side effects or “hidden state”), which are nested into each other and evaluated as necessary. In such cases, most “imperative logic” is abstracted away; the runtime handles the actual calling of functions, and any conditions in which one or the other overload of a function may need to be called. In such a program, the code isn’t thought of as “doing” something, it’s thought of as “being” something, and what exactly it is is determined as the program runs given initial input.
Higher-order functions are now a staple of many imperative languages as well; .NET’s lambda statements basically allow for “anonymous” functional input into another “function” (implemented imperatively but theoretically it doesn’t have to be), thus allowing for highly-customizable “chaining” of very general-purpose “functions” to achieve the desired result.
Another abstraction commonly seen in the latest round of programming languages is dynamic variable typing based on the concept of “duck-typing”; if it looks like a duck, swims like a duck, flies like a duck and quacks like a duck, you can call it a duck. Doesn’t matter if it’s actually a mallard or a canvasback. It MIGHT matter if it’s actually a goose or a swan, but then again it might not matter if all you care about is that it swims and flies, and kinda looks like a duck. This is considered the ultimate in object inheritance; you don’t care what it is, except to give it a name; what’s more important is what it does. In such languages there are basically only two types; the “atom”, a single element of information (one “value”; a number, character, function, whatever), and the “tuple”, composed of an atom and a “pointer” to everything else in the tuple. Exactly how these types are implemented in binary by the runtime is irrelevant; using these, you can achieve the functionality of virtually every type you can think of, from simple value types to strings to collections (which, since values can be of different “types”, allows for “complex types” aka “objects”).
1
One could consider Domain Specific Languages like SQL as a higher order of abstraction. SQL is a very targeted language that abstracts away operations such as storage and provides higher level functions based on set theory. Also consider how many mainstream languages today do not target a specific architecture but rather a Virtual Machine (such as the JVM or the .NET CLR). For an example C# is compiled to IL which is interpreted (or more frequently JIT’d –Just In Time Compiled– to a native implementation) by the native runtime engine.
There has been much hubbub about the concept of DSLs being used to create very high level languages that can be used without much technical experience to create a working program. Think if someone were able to describe their entities and interactions in close to plain English and the operating environment handled everything from presenting a simple UI, to storing data in a database of some kind. Once those operations become abstracted, you can imagine how effortless programming can become.
There are some in existence today such as JetBrains MPS (which is a toolkit for describing DSLs or a language generator). Microsoft had a brief foray (and very promising I might add) into this space with it’s M language (the M language was so complete that the language was defined in M).
Critics of the concept point to prior failed attempts to remove programmers from the job of developing programs, the difference with the DSL workbenches (as Fowler calls them) is that, developers would still be involved in codifying concepts that the Domain Experts could use to express the needs of their domain. Just like OS and Language vendors create tools that we use for programming, we would use DSLs to provide tools for business users. One could imagine DSLs that describe the data and logic, while developers create interpreters that store and retrieve data and apply the logic expressed in the DSL.
I would argue that meta-structures, modules, frameworks, platforms, and services are all higher-level feature groupings than classes. My hierarchy of programming system abstractions:
- services
- platforms, solution stacks
- frameworks
- modules, packages
- meta structures: metaclasses, higher order functions, generics, templates, traits, aspects, decorators
- objects, classes, data types
- functions, procedures, subroutines
- control structures
- lines of code
Meta-structures like metaclasses,
higher order functions, and
generics
clearly add abstraction to basic classes, functions, data types, and data instances. Traits, aspects, and decorators are newer mechanisms for combining code features, and similarly ‘accelerate’ other classes and functions.
Even pre-object languages had modules and packages, so putting them above classes might be debatable. Bu they contain those classes and meta-structures, so I rank them higher.
Frameworks are the meatiest answer–they orchestrate multiple classes, meta-structures, modules, functions, and such in order to provide sophisticated high-level abstractions. And yet frameworks still operate almost entirely in the realm of programming.
Solution stacks or platforms generally combine multiple frameworks, subsystems, or components into an environment for solving multiple problems.
Finally, there are services–often deployed as Web or network services. These are architectures, frameworks, solution stacks, or application capabilities delivered as complete bundles. Their internals are often opaque, primarily exposing administrator, programming, and user interfaces. PaaS and SaaS are common examples.
Now, this progression may not be entirely satisfying, for a few reasons. First, it makes a neat linear progression or hierarchy of things that aren’t perfectly linear or hierarchical. It covers some abstractions like “stacks” and service that are not entirely under developer control. And it doesn’t posit any new magic pixie dust. (Spoiler: There is no magic pixie dust.)
I think it’s a mistake to look just for new abstraction levels. All the ones I listed above have existed for years, even if they haven’t all been as prominent or popular as they now are. And over those years, the abstractions possible at every level of coding have improved. We now have general-purpose, generic collections, not just arrays. We loop over collections, not just index ranges. We have list comprehensions and list filter and map operations. Many language’s functions can have a variable number of arguments, and/or default arguments. And so on. We are increasing abstraction at every level, so adding more levels isn’t a requirement for increasing the overall level of abstraction.
The next abstraction after classes are meta classes. It’s that simple 😉
a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances. Not all object-oriented programming languages support metaclasses. Among those that do, the extent to which metaclasses can override any given aspect of class behavior varies. Each language has its own metaobject protocol, a set of rules that govern how objects, classes, and metaclasses interact…
2
I am surprised nobody has mentioned category theory.
The most fundamental unit of programming is the function which is based on types. Functions are usually denoted as f: A -> B, where A and B are types. If you put these things, which I’m calling types and functions, together in the right way you get something called a category. You don’t have to stop at this point.
Take these things, categories, and ask yourself what would be the correct way to relate them to each other. If you do it right you get something called a functor which goes between two categories and is usually denoted as F: C -> B. Once again you don’t have to stop.
You can take all functors and put them together in the right way and if you do things just right you start to wonder how to relate two functors to each other. At this point you get something called a natural transformation, mu: F -> G, where F and G are functors.
My knowledge at this point gets fuzzy but you can continue to do this and keep climbing the ladder of abstraction. Objects and classes don’t even come close to describing how high you can climb up the abstraction ladder. There are many languages that can express the above concepts computationally and the most prominent of those languages is Haskell. So if you really want to know what abstraction is really about then go learn some Haskell or Agda or HOL or ML.
I think the actor model is missing from the list of candidates.
Here’s what I mean by Actors:
- independent entities
- that receive messages, and when receiving a message, may
- create new actors,
- update some internal state for the next message,
- and send out messages
This model is something beyond deterministic Turing machines, and is actually closer to our real-world hardware when looking at concurrent programs. Unless you employ extra (costly) synchronization steps, these days, when your code receives data, that value may already have changed on the other side of the same die, maybe even inside the same core.
Short discussion/introduction: http://youtube.com/watch?v=7erJ1DV_Tlo
1
If I understand you correctly, your “ascending abstractions” can be thought of as increasingly large encapsulations of logic, mostly related to code reuse.
From specific instructions executed one after the other, we move to functions/subroutines, which encapsulate, or abstract, a logical grouping of instructions into a single element. Then we have objects, or modules, which encapsulate subroutines relating to a certain logical entity or category, so I can group all string operations under the String
class, or all common math operations under the Math
module (or static class, in languages such as C#).
So if that’s our progression, what comes next? Well, I don’t think you have a clear-cut next step. As others have answered, your progression applies only to imperative/procedural programming styles, and other paradigms don’t share your concepts of abstraction. But if I there’s something that can logically extend your metaphor, it’s services.
A service is similar to a class in the sense that it’s an entity that exposes functionality, but it implies a much stricter separation of concerns than the back-and-forth with objects you instantiated yourself. They expose a limited set of operations, hide the internal logic, and are not even necessarily running on the same machine.
Again, there is a fine distinction. In most cases, you will use an object that acts as a proxy to a service, and the two will be very similar, but as an architecture, the two are distinct.
New forms of abstraction hide low-level work from you. Named procedures and functions hide program addresses from you. Objects hide dynamic memory management and some type-dependent “if statements”.
I’d suggest that the next level of practical abstractions that will hide low-level drudgery from you are those of functional reactive programming. Look at “signals” in something like http://elm-lang.org/ which hide the callbacks and update dependencies you would have to explicitly manage in javascript. FRP can hide a lot of the complexity of inter-process, and inter-machine communication that’s needed in large-scale internet applications and high-performance parallelism too.
I’m pretty sure that this is what we’re all going to get excited about in the next 5 years or so.
1
Set theory — as partially implemented in relational databases, but also in statistics languages like SAS and R, provide a different but arguably higher level of abstraction than OO.