Why is this series of articles, which is supposed to be about game engine architecture, so obsessed with configuration languages?
Because mastering abstraction will let you solve categories of problems, even problems you have not thought of yet. And more importantly, it will let you write the tools to let other people solve those problems. So striving for excellence in writing a configuration language is tantamount to being the ultimate team player and collaborator.
And furthermore you have to do so in a way that augments the underlying language and simplifies expressions of the problems at hand. If you end up just rewriting the underlying operating system and the C++ programming language, you will have failed. But you must be capable of making that mistake before you can make the game engine correctly. So, for now we will focus on how to create a system that supports abstraction, then later worry about how to make that more useful than the underlying system. (We will eventually realize that we need lots of little domain-specific languages, so we need to be able to write languages in our sleep.)
Abstraction comes in handy when you strive to solve problems that you have not thought of yet, so we need to nail it.
As I mentioned in article 5, Abelson summarizes a framework for abstraction in computer languages, which I paraphrased:
| Procedures | Data | |
|---|---|---|
| Primitives | +, *, == | numbers, strings, true/false |
| Means of Combination | if, while, algebraic expressions with more than one operator, ... | Arrays, lists, dictionaries |
| Means of Abstraction | function definitions | Classes |
Today I just want to mention how this taxonomy fits with the FIEA game engine configuration language:
| Procedures | Data | |
|---|---|---|
| Primitives | Action | int, float, vector, matrix |
| Means of Combination | ActionList | table (a.k.a. scope) |
| Means of Abstraction | Reaction | Entity (?) |
(This table leaves out some details. For example, Action, Reaction and Entity are all tables (or, if you prefer, they have tables). So there is more to the story than this table shows. But that's okay; this table is meant to see how things fit together, not to describe the entire system.)
But how does Entity provide a means of data abstraction beyond what "table" provides? And why do I put a question mark there? What's missing from a table that Entity provides that allows a table to graduate from merely a means of combination into a means of abstraction?
For that matter, what does "abstraction" mean?
When solving problems, abstraction means that you have taken a collection of concrete things, found what they have in common and rephrased the problem so that you can use generalities instead of specifics, and still formulate a viable solution.
The question also relates to what "concrete" means. And this is where things unravel for a moment.
Those familiar with Object-Oriented Programming (OOP) might readily identify how the dichotomy of class-object correlates with abstract-concrete: A class defines how data is structured and how to perform operations on that data, and combined this defines a "type", which is abstract. An object, that is an instance of that class, which defines a "token", is concrete. So that seems to explain what "abstract" and "concrete" mean in the context of programming languages.
Almost, but not quite.
Again, those familiar with OOP with also know that an "abstract class" is one that cannot be instantiated and we usually call a class that can be instantiated a "concrete class". So here again we have the abstract-concrete dichotomy but in this context, both the abstract and the concrete are classes. Abstract classes have at least one pure virtual method and concrete classes define the bodies of all methods. This ambiguity seems to muddle the concept of "abstraction".
Ironically, in order to understand how these 2 different notions of abstraction fit together, we have to see what they have in common, that is, we have to make an abstraction of both forms of abstraction.
Aside from the pure fun of getting this far into mental orbit, why do we need to converge these 2 different notions of abstraction? Because our configuration language is prototype-oriented, not object-oriented, so the whole class-object dichotomy does not exist, and neither does the abstract-class-versus-concrete-class distinction. But those distinctions apparently have utility so we need to understand what features they provide so that we can provide them in our configuration language.
This exercise reminds me of trying to understand what "meaning" means. Or defining the term "define".
Let's introduce the term generalization because it means basically the same thing as "abstraction", and now that we've gotten philosophical and unveiled 2 different forms of abstraction, we need to anchor ourselves.
Also, let's make analogies to non-computing situations. What does "generalization" mean in real life? And what is the opposite of "generalization"?
The opposite of "generalization" is specialization.
Imagine trying to explain to somebody what "dad" means. You point to its father and say "dad".
Now imagine trying to explain to a baby what the word "that" means. What do you point at? What do you say?
So it seems we start with the specific and work our way towards the general. We cannot point to the general; we can hope that other people will have the same impression of what we mean when we generalize, and we can define the general in terms of collections of specifics.
By the way, we just made an attempt to define what "define" means; for a first pass we can say that a "definition" is an "association between a proxy and a thing", where the proxy (also called a definiendum) might be a word or symbol, and the thing (also called the definiens) could be anything -- abstract or concrete, general or specific -- and could include some combination of things. For our purposes, you could say that "define" means "associate".
When you define a thing, you refer to the components of the thing. So those components must also have a definition. Eventually you run into things that you cannot define in terms of other things, and those are the primitives, i.e. the things that are innate to the system, such as integers and addition.
Back to the question at hand: What do "abstract data types" have in common with "abstraction in object-oriented programming"? Abstract data types have virtual methods which effectively get bound at run-time. Meanwhile, the notion of abstraction means that you can have multiple objects of the same class, all of which respond to the same operations.
These both have in common the idea that objects have methods.
Data abstraction lets you specialize the details of a generalization of a verb. This is also known as polymorphism. In contrast, OOP abstraction simply lets you operate on multiple instances of the same class using the same verbs.
In both cases, "abstraction" amounts to using the same symbol (method/verb) to operate on different objects.
And to perform those operations, the procedure that defines the operation needs to know something about its operand (the data).
In C++ this basically amounts to saying that the method receives a "this" pointer.
So, in our configuration language, the difference between a "table" (the means of combination) and an Entity (a means of abstraction) is that the methods of an Entity have access to the particular object (i.e. table or Entity) upon which the method operates.
And that all comes back to binding, which I discussed ad nauseum in previous articles. But I wonder if I covered it sufficiently.

No comments:
Post a Comment