Response by Mary Shaw
Open Implementations --
Abstractions, Separation of Concerns, and Degrees of Control
Open Implementations
When I think about open implementations, I think about machinery with
maintenance panels as well as user controls:
- color/hue/brightness controls on an TV
- equalizer sliders on a sound system
- telephone wire cabinet in an office
- or, in software:
- the switches (initialization parameters)
of, e.g., a unix filter
- the type parameter of a generic definition
(e.g, stack[T] --> stack[int])
- the format-conversion modules that allow a word
processor like Word to deal with formats from
foreign word processors (probably the auxiliary
tools in Photoshop-land are like this, but I have
no direct experience)
- callbacks or upcalls in layered systems, or
functional parameters -- all are ways of providing
your own computation to be executed by some component
you are invoking
Interestingly enough, the things that spring immediately to my mind are not
dominated by performance tuning -- but most of the examples in the foil are.
I believe
Figure 4.
The Essential Question
The guiding principle of software requirements is that they should state
*what* the system should do and should not make decisions about *how* to do
it. The trouble is, in many cases it's very, very hard to isolate the
abstract function. People are pretty concrete, and (a) the implied
generalization step is tough; (b) the language available for most
requirements lends itself to giving examples for definiteness.
The same principle holds at any specification/implementation boundary: the
specification makes the guarantees, the implementation achieves them.
There's clearly a tradeoff: the more flexible the specification is, the more
likely it is to be broadly useful, but the more constrained the
implementation will be. No free lunch: Better value in the component means
higher constrtuction costs.
Seems to me that the essential question here is:
How can we separate essential properties (at any stage of the design) from
inessential or incidential ones, allowing the latter to be provided later?
Here are some questions to help focus on the distinction:
- What information should be available, to whom, and when? Who
provides it?
- How can this be done without adverse interactions or other
interference? (This is why single-hierarchy defintion chains
are so much simpler than multiple chains; it's why reconciling
different design views is hard.)
- How do you decide how much generality/flexibility to provide?
- When can various kinds of decisions be bound (i.e., what can
be meta-parameters, and are they handled at compile or execute
time?)
Freedom vs License
It is suggested that breaking the black box boundary and allowing the
meta-interface any control over the semantics allows arbitrarily bad things
to happen and should therefore be disallowed. However:
- Performance sometimes *is* part of the semantics, so the
boundary is unclear.
- Those aren't the only choices. The meta-interface can state
restrictions on its use. That is, not only can the door be
open or shut, it can be open but have a receptionist or bouncer.
The "generator" construct in Alphard is an example. It separated the loop
construct into three parts:
- the essential init-(test-advance)* pattern (which is the
responsibility of the language),
- the definitions of init, test, and advance (which are the
responsibility of the designer of a structure that supports
iteration), and
- the loop body (which is the responsibility of the programmer).
But it gets better: the set {init, test, advance} was required to satisfy a
requirement. Specifically, a theorem had to be true of their joint pre- and
post-conditions. In exchange, you got a case-specific Hoare-style proof
rule for the resulting loops. You could *not* break the essential semantics
of loops, but you could tune it to your needs.
It is also suggested that the designer of a language/metalanguage should try
to prevent bad programs from being written. This is not a useful goal, as
perverse programmers will find a way to do Bad Things with any tool you give
them. A better goal is to design the language to help programmers express
their designs clearly and concisely, without requiring unnecessary decisions
at any point.
Abstraction at the Meta-interface
The foil doesn't say much about the level of abstraction at the
meta-interface. This does require attention. We learned this when we
designed a symbol table in Alphard. [Alphard was an abstract data type
langauge that explicitly separated the formal specification of a component
from its implementation. The specification presented the abstract model,
the implementation the code. There was an explicit abstraction function to
connect the two.]
The specification of the symbol table included among its initialization
parameters an integer. The integer was not mentioned any place in the
specification, so it appeared to have no effect -- a useless parameter.
Well, the integer controlled the size of a hash table.
It affected performance rather than functionality -- the specs didn't
address anything except functionality, so this integer had no role.
Further, even explaining the integer as hash table size would have been
wrong -- there's no reason the user of a symbol table should know about the
implications of hash table size. It would have been better to express the
parameter as a tuning function, assigning values with understandable effect
on speed and converting them internally into hash table size. Or something
else.
Structured interfaces
My control access panel analogy leads to a further question: why just
interface and metainterface? After all, a physical system might have
maintenance panels for mechanical, electrical, hydraulic, engine...
More to the point, a software component might well have (at least
conceptually) interfaces for:
- the overt function (e.g., airline reservations)
- system management (e.g., changing flights and schedules)
- financial management (e.g., changing fares)
- billing (e.g., charges and commissions)
- audit trails (e.g., access records for security)
- performance monitoring (e.g., load leveling, system tuning)
- emergency operation (maybe not so important here, but in a
chemical plant they care a lot)
- and probably others
These may be scrambled together in such a way that it's hopeless to sort
them out, but conceptually they're often there)
Now in some ways each of these except the first looks like a meta-interface.
You can certainly make both data changes and policy changes without
affecting the general nature of the overt function. I don't think you want
to join them in a single meta-interface. First, that would violate
separation of concerns. Second, it would probably violate operating policy.
A good concrete example is the Scribe text formatter. It was of the
*ROFF/PUB generation, came on the scene a little before TeX. What Scribe
got *really* right was the separation between: the document, the style in
which the document is formatted, and the device on which the document is
printed. The same notation was used for all three. The differences
involved what you could control. The style and the device are both
meta-stuff, but it's important to keep them separate.
Another thing Scribe got right was incrementality. You could make small
changes in the style with small amounts of knowledge. You could make large
changes in the style with modest amounts of knowledge. You could do truly
arcane things if you wanted to badly enough. In another community this
property is called the "gentle-slope system" property, referring to the
slope of the learning curve required to use the system well, or at all.
Back to Alphabetical List of Responses
Back to Main OI Workshop Page
Back to OI Home Page
Mary Shaw,
Mary_Shaw@cs.cmu.edu
(Last Revised March 1996)