Software is the combinatorial part of computers. It allows all useful jobs to be done, and all human-computer interaction to happen. But building all computer software from scratch is no task for everyone; people always write software from some commonly known state, called system software; they also almost always use a computing system (most often the same) for this.
This paper aims at demonstrating that while currently available computing system software provides a lot of expedient services, their low-level structure forbids them to provide useful services, and leads to huge, inefficient, slow, unusable, unportable, unmaintainable, unupgradeable, software. This paper proposes to explain why the current design of "system software" is deeply and unrecoverably flawed, and shows a new way for designing computing systems that achieve real usefulness, using well-known existing techniques.
Utility is a moral concept; it directly depends on the goal you defined for general interest. Actually, Utility is as well defined by the moral concept of Good, as Good is defined by Utility; to maximize Good is to maximize Utility. We'll admit that all the previous objects described as valuable (time, effort, etc) are indeed valuable as far as general interest is concerned.
Thus, the qualities of a (programming) language do not lie in what can be done with the language (the language being hopefully Turing-equivalent with libraries to access all the hardware (i.e. able to express anything), nor in the efficiency of a straightforward implementation (i.e. easy access to beginners), as later a good "optimizing" compiler can always be achieved or speed critical routines can be included in libraries (i.e. if you really need a language, then you won't be a beginner for a long time). Those qualitise lie in the easiness to express new concepts, and to modify existing routines. With this in mind, a programming language is better than another if it is easier for a human to write a new program or to modify an existing program, or of course to reuse existing code (which is some extension to modifying code); a language is better, if you can sentences of equal accuracy are shorter, or if just if better accuracy is reachable.
Let no computer be the basic reference for computer utility. Now, can computers be useful at all ? Well yes, indeed, as they can quickly and safely add or multiply tons of numbers (that's called number-crunching) that no human could handle in decent times and be sure not to commit mistakes. But number-crunching is not the only utility of computers. Computers can also memorize large amount of data, and remember them much more exactly and unerringly than humans. But all these utilities are related to quantity: managing quickly large amounts of information. Can computers be more useful than that ?
Of course, computer utility has got limits; and those limits are those of the human understanding, as computers are creatures of the human. The dream of Artificial Intelligence (AI), older than computer itself, is to push those limits beyond the utility of humans themselves, that is, Human beings creating something better than (or as good as) themselves. But, human-like computer utility won't evolve from nil to beyond human understanding at the first try; it will have to reach every intermediate states before it may ever attain AI (if it ever does).
So as for quality vs quantity, yes, computers can be useful. By their mechanical virtues of predictability, they can be used to verify consistency of mathematical theorems, to help people schedule their work, to help them diminish the number of mistakes they do. But do note that the same predictability virtues makes AI difficult to attain, for the limits of humans intelligence is far beyond the limits of what it can predict. However this is yet another problem, out of the range of this paper...
The basic or common state of software, shared by a large number of
different computers, is called their Operating System.
This concept does not mean that some part of the software is exactly the same
accross different computers; indeed, what does "the same" shall mean ?
Some will want software of a computer to be exactly the same,
which is a very limited definition, that furthermore is often meaningless:
for example, if the two computers have in common a software
part full of useless zeros, are they using the same system ?
However, even if the computers use completely different technologies,
you can sometimes say that they share the same behavior, that they
manipulate similarly the same kind of objects.
Let's use our human analogy again: can you find cells that are "the same" on
different humans ? No, and even if you found exactly the same fat on two
humans, it wouldn't be meaningful. But you can nonetheless say that those two
humans share the same ideas, the same languages and
idioms or colloquialisms, the same
Cultural Background.
It's the same with computers; even though computers may be of completely
different models (and thus cannot be a copy one of one another),
they may still share a common background, be able to communicate
and understand each other, to react similarly to similar environments.
That we called the Operating System.
We see that actual OS limits are very dull, for "what is shared" depends on the class of computers considered, and the criteria used. Some people try to define the OS as a piece of software providing some list of services; but they then find their definition ever evolving, and never agree when it comes to having a definition that matches truely different systems. That's because they have a very narrow-minded self-centric view of what a operating system is. The ancient Chinese, or the ancient Greek did not learn the same customs, language, habits, did not read the same books; but either had a culture and no one could ever say that one was better than the other, or that one wasn't really a culture. Even if their culture is less sophisticated than ours, our ancestors, or some native tribes of continents recently discovered by the white, all did/do have a culture. As for computers, you cannot define being an OS in terms of what exactly an OS should provide (eating with a fork or accepting a mouse as a pointing input device), but in terms of if it is a software background common to different computers, and accessible to all of them. When you consider all possible humans or computers, the system is what they have in common: the hardware, their common physical characteristics; when you consider a given computer or individual, the system is just the whole computer or individual.
That's why the power and long-term utility of an OS musn't be measured according to what the OS does currently allow to do, but according to how easily it can be extended so that more and more people share more and more complex software. That is, the power of an OS is not expressed in terms of services it statically provide, but in terms of services it can dynamically manage; intelligence is expressed not in terms of knowledge, but in terms of evolutivity toward more knowledge. A culture with a deep knowledge but that would prevent further innovations, like the ancient chinese civilization, would indeed be quite harmful. An OS providing lots of services, but not allowing its user to evolve would likewise be harmful.
Again, we find the obvious analogy with human culture for which the same stands; the analogy is not fallacious at all, as the primary goal of an operating system is allowing humans to communicate with computers more easily to achieve better software. So an operating system is a part of human culture, a part which involves the computers that run under this OS.
As a blatant example of the lack of evolution of system software quality is the fact that the most popular system software in the world (MS-DOS) is a twenty-year old thing that does not allow the user to do either simple task, or complicated ones, thus being a no-operating system, and forces programmers to rewrite low-level tasks everytime they develop any non-trivial program, while not even providing trivial programs. This industry-standard has always been designed as a least sub-system possible for the Unix system, which itself is a system made of features assembled in undue ways on top of only two basic abstractions, the raw sequence of bytes ("files"), and the ASCII character string; Unix has had a huge number of new special files and bizarre mechanisms added to allow access to new kind of hardware or software abstractions, but its principles are still those unusable unfriendly files and strings, that were originally thought as a minimal abstractions to fit the tiny memory of then existing computers, and not as an interesting concept for today's computers; now known as POSIX, it is the new industry standard OS to come.
It may be said that computing has been doing quantitative leaps, but has not done any comparable qualitative leap; computing grows in extension, but does not evolve toward intelligence; it sometimes rather becomes more largely stupid. This is the problem of operating systems not having a good kernel: however large and complete their standard library, their utility will be essentially restricted to the direct use of the library.
This phenomenon can also be explained by the fact that programmers, long used to software habits from the heroic times when computer memories were too tight to contain more than just the specific software you needed (when they even could), do not seem to know how to fill today computers' memory, but with pictures of gorgeous women and digitized music (which is the so-called multimedia revolution). Computer hardware capabilities evolved much quicker than human software capabilities; thus humans find it simpler to fill computers with raw data (or almost raw data) than with intelligence, especially since companies put a "proprietary" label on any non-trivial software they produce.
We already see how such a conception fails: it would perhaps be perfect for a finite unextensible static system; but we fell it may not be able to express a dynamically evolving system. However, a solid argument why it shouldn't be able to do so is not so obvious at first sight. The key is that like any complex enough systems, like human beings, computer have some self-knowledge. The fact becomes obvious when you see a computer being used as a development system for programs that will run on the same computer. And indeed the exceptions to that "kernel" concept are those kind of dynamic languages and systems that we call "reflexive", that is, that allow dynamical manipulation of the language constructs themselves: FORTH and LISP (or Scheme) development systems, which can be at the same time editors, interpreters, debuggers and compilers, even if those functionalities are available separately, so there is no "kernel" design, but rather an integrated OS.
And then, we see that if the system if powerful enough (that is, reflexive), any knowledge is can be applied to the system itself; any knowledge is also self-knowledge; so it can express system structure. As you discover more knowledge, you also discover more system structure, perhaps better structure than before, and certainly structure that is more efficiently represented directly than through stubborn translation to those static kernel constructs. So you can never statically settle once and for all the structure of the system without ampering the system's ability to evolve toward a better state; any structure that cannot adapt, even those you trust the most, may eventually (though slowly) become a burden as new meta-knowledge is available. Even if it actually won't, you can never be sure of it, and can expect only refutation, never confirmation of any such assumption.
The conclusion to this is that you cannot truely separate a "kernel" from a "standard library" or from "device drivers"; in a system that works properly, all have to be integrated into the single system concept. Any distinction inside the system is purely arbitrary (but it is sometimes expedient to have arbitrary decisions).
Thus
Some will say that an operating system should only provide the device drivers, the input/output system. But ...
....
Some say that common users are too stupid to program; that's only despising them; most of them don't have time and mind to learn all the subtleties of advanced programming; but they often do manually emulate macros, and if shown once how to do it, are very eager to write their own macros/aliases.
Some fear that authorizing a "mere" user to use a powerful programming language is the door open to piracy and system crash. Well, if the language library has such security holes, it's a library misconception; if the language doesn't allow the design of a secure library, that's a language misconception. Whatever was misdesigned, it language should be redesigned, amended of replaced (as should be "C"). If you don't want people to cross an ill-placed fissured wall, you'd better rebuild a new better placed wall than hire an army of guards to shoot at people trying to get into the other part of the misplaced wall, or unwillingly trespassing in a crack -- and in the first solution, don't forget to also hire people to cope with lacks due to the wall's misplacement. And if what you want most is nobody trespassing, well, just forbid people from ever nearing the wall -- don't have them use the computer.
The truth is any computer user, whether a programming guru or a novice user, is somehow trying to communicate with the machine. The easier the communication, the quicker better larger the work is getting done.
So, we see that 99.99% of programming time throughout the world is spent doing again and again the same basic things that's been done hundreds of times before or elsewhere. It is common to hear (or read) that most programmers spend their time reinventing the wheel. Of course, you can't escape asking students to repeat what their elders did, so they can understand it and interiorize the constraints of computing. The problem is student programming represents less than 50% of programming time (and is not the most used thereafter), which means even professional people are spending most of their time writing again and again new versions of earlier works, nothing really "worth" the time they spend -- moreover, new work is often done by students or their elder equivalent, researchers, which aggravate the time share professionals spend doing things really new.
Now, after all, you may think that such a situation creates jobs, so is desirable; so why bother ?
First of all, there is plenty of useful work to do on Earth, so time and money saved by not repeating things while programming can be spent on many many other activities (if you really can't find any, call me, I'll show you).
Now, rewriting is a serious problem for everyone. First of all, rewriting is a loss of time, that make programming delays quite longer, thus are very costly. More costly even is the fact that rewriting is an error prone operation and anytime while rewriting, one may introduce errors very difficult to trace and remove (if need be, one may recall the consequences of computer failures in space ships, phone nets, planes). Reuse of existing data accross software rewrites, and communication of data between different software proves being of exorbitant cost. The most costly aspect of rewriting may also be the fact that any work has a short lifespan, and will have to be rewritten entirely from scratch whenever a new problem arises; thus programming investment cost is high, and software maintenance is of high cost and low quality. And it is to be considered that rewriting is an ungrateful work that disheartens programmers, which has an immeasurably negative effect on programmer productivity and work quality. Last but not least, having to rewrite from scratch creates an arbitrary limit to software quality, that is no software is better than what one man can program during one life.
Therefore, it will now be assumed as proven proved that code rewriting is a
really bad thing, and that we thus want the opposite: software reuse.
We could have arrived at the same conclusion just with this simple argument:
if some software is really useful (considering the general interest), then it
must be used many, many times, by many different people, unless it is some
kind of computation with a definitive answer than concerns everybody (which
is difficult to conceive: some software that would solve a metaphysical or
historical problem !). Thus, useful software, least it be some kind of very
unique code, is to be reused countless times. That's why to be useful, code
must be very easy to reuse.
It will be showed that such reuse is what the "Object-Orientation" slogan is all about, and what it really means. But reuse itself introduces new problems that have to be solved before reuse can actually be possible: how can we reuse software without spreading errors from reused software, without introducing errors due to misunderstanding or misadaptation of old code, and without having software obsolescence ? Let's see what are possible reuse techniques, and how they cope with these problems.
First of all, copying is a tedious method; if you have to copy and modify the same piece of code thousands of time, if can prove a long and difficult work. Now, copying is an error-prone method: nothing will prevent you from doing mistakes while copying or modifying.
As for the moral or economical objection, it is sometimes considered bad manners to copy other people's code, especially when copyright issues are involved; sometimes code is protected in such a way that one cannot copy it easily (or would be sued for doing that); thus this copy-paste method won't even be possible everytime.
But even if the previous problems are solved (which is not obvious at all), there still is a big problem about code copying: uncontrolled propagation of bugs and lacks of feature accross the system. And this is quite a serious threat to anything like code maintenance; actually, copying code means that any misfeature in the code is copied altogether with intended code. So the paradox of code copying is that bad copying introduces new errors, while good copying spreads existing errors; in any case code copying is an error prone method; and even error correction is made very difficult, because every copy of the code must be corrected with the proper modification; and tracking down all existing copies is especially difficult as code must have been modified (else the copy is made useless by any macro-defining preprocessor or procedure call in any language); moreover, if another programmer (or the same programmer some time later) ever wants to modify the code, he may be unable to find all the copies.
To conclude, software creation and maintenance is made very difficult, or even impossible, when using copy-paste; thus, this method is definitely bad for anything but exceptional reuse of a small number of mostly identical code in a context where expediency is much more important than long-term utility. That is, copy-paste is good for "hacking" small programs for immediate use; but it's definitely not a method to program code meant to last or to be widely used.
It's like vocabulary and culture: you always need people to write dictionaries, encyclopaedias, and reference textbooks; but these people just won't invent new knowledge and techniques, they only establish means to communicate existing ones more easily. You still need other people to create new things; you just can't wait for what you need to be included in the next revision of such reference book; and it won't be if someone doesn't settle things before they can be considered by the standardization commitees.
Now how will a dictionary be used ? How will the creating people, or even the common people work ? They won't just use isolated words from the dictionary; they will combine such words. And that is grammar -- the structure of the language. So a library is great for reuse, but actually, a good grammar is essential to use itself, and reuse in particular. Thus what does reuse mean for the language grammar ? It means that you can define new words from existing ones, thus creating new contexts, in which you can talk more easily about your particular problems. The grammar should make it clear what your sentences mean given the context, while there should be some way that people with different context backgrounds, but talking about the same problem will be able to communicate anyway to help each other. For the same problems always arise in multiple places at the same time, and different contexts are always built to solve them; and you don't build contexts just for the sake of building contexts, but to solve the problems; thus, before a standard context is settled than solves the problem, you more than ever need being able to talk accross different contexts, i.e. different extensions to standard libraries.
Centralized code is also called "client-server architecture"; the central code is called the server, while those who use it are called clients. And we saw that a function server is definitely something that no sensible man would use directly; human users tend to write a library that will encapsulate calls to the server. but it's how most operating systems and net-aware programs are implemented, as it's the simplest implementation way. Many companies boast about providing client-server based programs, but we see there's nothing to boast about it; client-server architecture is the simplest and dumbest mechanism ever conceived; even a newbie is able to do that easy. What they could boast about would be not using client-server architecture, but truely distributed yet dependable software.
A server is nothing more than a bogus implementation for a library, and shares all the disadvantages and limits of a library, with enhanced extensibility problem, and additional overhead. It's only advantage is to have a uniform calling convention, which can be useful in a system with centralized security, or to pass the stream of arguments through a network to allow distant client and servers to communicate. This last use is particularly important, as it's the simplest trick ever found for accessing an object's multiple services through a single communication line. Translating software interface from library to server is called multiplexing the stream of library/server access, while the reverse translation is called demultiplexing it.
Multiplexing means to split a single communication line or some other resource into multiple sub-lines or sub-resources, so that this resource can be shared between multiple uses. Demultiplexing is recreating a single line (or resources) from those multiple ones; but as dataflow is often bi-directional, this reverse step is most often unseparable from the first, and we'll only talk about multiplexing for these two things. Thus, multiplexing can be used to share a multiple functions with a single stream of calls, or convertly to have a function server be accessed by multiple clients.
Traditional computing systems often allow multiplexing of some physical resources, thus spliting them into a first (but potentially very large) level of equivalent logical resources. For example, a disk may be shared with a file-system; CPU time can be shared by task-switching; a network interface is shared with a packet-transmission protocol. Actually, what any operating system does can be considered multiplexing. But those same traditional computing systems do not provide the same multiplexing capability for arbitrary resource, and the user will eventually end-up with having to multiplex something himself (see the term user-level program to multiplex a serial line; or the screen program to share a terminal; or window systems, etc), and as the system does not support anything about it, he won't do it the best way, and not in synergy with other efforts.
What is wrong with those traditional systems is precisely that they only allow limited, predefined, multiplexing of physical resources into a small, predefined, number of logical resources; there they create a big difference between physical resources (that may be multiplexed), and logical ones (which cannot be multiplexed again by the system). This gap is completely arbitrary (programmed computer abstractions are never purely physical, neither are they ever purely logical); and user-implemented multiplexers must cope with the system's lacks and deficiencies.
So we see that system designers are ill-advised when they provide such specific multiplexing, that may or may not be useful, whereas other kind of multiplexing is always needed (a proof of which being people always boasting about writing -- with real pain -- "client/server" "applications"). What they really should provide is generic ways to automatically multiplex lines, whenever such thing is needed.
More generally a useful operating system should provide a generic way to share resources; for that's what an operating system is all about: sharing disks, screens, keyboards, and various devices between multiple users and programs that may want to use those accross time. But genericity is not only for operating systems/sharing. Genericity is useful in any domain; for genericity is instant reuse: your code is generic -- works in all cases -- so you can use it in any circumstances where it may be needed, whereas specific code must be rewritten or readapted each new time it must be used. Specificity may be expedient; but only genericity is useful on the long run.
Let us recall that genericity is the property of writing things in their most generic forms, and having the system specialize them when needed, instead of hard-coding specific values (which is some kind of manual evaluation).
Now, How can genericity be achieved ?
First, we see that the same algorithm can apply to arbitrarily complex data
structures; but a piece of code can only handle a finitely complex data
structure; thus to write code with full genericity, we need use code as
parameters, that is, second order. In a low-level language (like "C"),
this is done using function pointers.
We soon see problems that arise from this method, and solutions for them. The first one is that whenever we use some structure, we have to explicitly give functions together with it to explain the various generic algorithm how to handle it. Worse even, a function that doesn't need some access method about an the structure may be asked to call other algorithms which will turn to need know this access method; and which exact method it needs may not be known in advance (because what algorithm will eventually be called is not known, for instance, in an interactive program). That's why explicitly passing the methods as parameters is slow, ugly, inefficient; moreover, that's code propagation (you propagate the list of methods associated to the structure -- if the list changes, all the using code changes). Thus, you mustn't pass explicitly those methods as parameters. You must pass them implicitly; when using a structure, the actual data and the methods to use it are embedded together. Such a structure including the data and methods to use it is commonly called an object; the constant data part and the methods, constitute the prototype of the object; objects are commonly grouped into classes made of objects with common prototype and sharing common data. This is the fundamental technique of Object-Oriented programming; Well, some call it that Abstract Data Types (ADTs) and say it's only part of the "OO" paradygm, while others don't see anything more in "OO". But that's only a question of dictionary convention. In this paper, I'll call it only ADT, while "OO" will also include more things. But know that words are not settled and that other authors may give the same names to different ideas and vice versa.
BTW, the same code-propagation argument explains why side-effects are an especially useful thing as opposed to strictly functional programs (see pure ML :); of course side effects complicate very much the semantics of programming, to a point that ill use of side-effects can make a program impossible to understand or debug -- that's what not to do, and such possibility is the price to pay to prevent code propagation. Sharing mutable data (data subject to side effects) between different embeddings (different users) for instance is something whose semantics still have to be clearly settled (see below about object sharing).
The second problem with second order is that if we are to provide functions
other functions as parameter, we should have tools to produce such functions.
Methods can be created dynamically as well as "mere" data, which is all the
more frequent as a program needs user interaction. Thus, we need a way to
have functions not only as parameters, but also as result of other functions.
This is Higher order, and a language which can achieve this has a
reflexive semantics. Lisp and ML are such languages; FORTH also, whereas
standard FORTH memory management isn't conceived for a largely dynamic use of
such feature in a persistent environment. From "C" and such low-level
languages that don't allow a direct portable implementation of the
higher-order paradygm through the common function pointers (because low-level
code generation is not available as in FORTH), the only way to achieve
higher-order is to build an interpreter of a higher-order language such as
LISP or ML (usually much more restricted languages are actually interpreted,
because programmers don't have time to elaborate their own user customization
language, whereas users don't want to learn a new complicated language for
each different application and there is currently no standard user-friendly
small-scale higher-order language that everyone can adopt -- there are just
plenty of them, either very imperfect or too heavy to include in every
single application).
With respect to typing, Higher-Order means the target universe of the language is reflexive -- it can talk about itself.
With respect to Objective terminology, Higher-Order consists in having classes as objects, in turn being groupable in meta-classes. And we then see that it _does_ prevent code duplication, even in cases where the code concerns just one user as the user may want to consider concurrently two -- or more -- different instanciations of a same class (i.e. two sub-users may need toe have distinct but mostly similar object classes). Higher-Order is somehow allowing to be more than one computing environment: each function has its own independant environment, which can in turn contain functions.
To end with genericity, here is some material to feed your thoughts about
the need of system-builtin genericity: let's consider multiplexing.
For instance, Unix (or worse, DOS) User/shell-level programs are ADTs,
but with only one exported operation, the "C" main() function per executable
file. As such "OS" are huge-grained, with ultra-heavy inter-executable-file
(even inter-same-executable-file-processes) communication semantics no one can
afford one executable per actual operation exported. Thus you'll group
operations into single executables whose main() function will multiplex those
functionalities.
Also, communication channels are heavy to open, use, and maintain, so you must explicitly pass all kind of different data & code into single channels by manually multiplexing them (the same for having heavy multiple files or a manually multiplexed huge file).
But the system cannot provide builtin multiplexing code for each single program that will need it. It does provide code for multiplexing the hardware, memory, disks, serial, parallel and network lines, screen, sound. POSIX requirements grow with things a compliant system oughta multiplex; new multiplexing programs ever appear. So the system grows, while it will never be enough for user demands as long as all possible multiplexing won't have been programmed, and meanwhile applications will spend most of their time manually multiplexing and demultiplexing objects not yet supported by the system.
Thus, any software development on common OSes is hugeware. Huge in hardware resource needed (=memory - RAM or HD, CPU power, time, etc), huge in resource spent, and what is the most important, huge in programming time.
The problem is current OSes provide no genericity of services. Thus they can never do the job for you. That why we really NEED generic system multiplexing, and more generally genericity as part of the system. If one generic multiplexer object was built, with two generic specializations for serial channels or flat arrays and some options for real-time behaviour and recovery strategy on failure, that would be enough for all the current multiplexing work done everywhere.
So this is for Full Genericity: Abstract Data Types and Higher Order.
Now, if this allows code reuse without code replication -- what we wanted --
it also raises new communication problems: if you reuse objects especially
objects designed far away in space or time (i.e. designed by other
people or an other, former, self), you must ensure that the reuse is
consistent, that an object can rely upon a used object's behaviour. This is
most dramatic if the used object (e.g. part of a library) comes to change
and a bug (that you could have been aware of -- a quirk -- and already have
modified your program accordingly) is removed or added. How to ensure object
combinations' consistency ?
Current common "OO" languages are not doing much consistency checks. At most, they include some more or less powerful kind of type checking (the most powerful ones being those of well-typed functional languages like CAML or SML), but you should know that even powerful, such type checking is not yet secure. For example you may well expect a more precise behavior from a comparison function on an ordered class 'a than just being 'a->'a->{LT,EQ,GT} i.e. telling that when you compare two elements the result can be "lesser than", "equal", or "greater than": you may want the comparison function to be compatible with the fact of the class to be actually ordered, that is x<y & y<z => x<z and such. Of course, a typechecking scheme, which is more than useful in any case, is a deterministic decision system, and as such cannot completely check arbitrary logical properties as expressed above (see your nearest lectures in Logic or Computation Theory). That's why to add such enhanced security, you must add non-deterministic behaviour to your consistency checker or ask for human help. That's the price for 100% secure object combining (but not 100% secure programming, as human error is still possible in misexpressing the requirements for using an object, and the non-deterministic behovior can require human-forced admission of unproved consistency checks by the computer).
This kind of consistency security by logical formal property of code is called a formal specification method. The future of secure programming lies in there (try enquire in the industry about the cost of testing or debugging software that can endanger the company or even human lives if ill written, and insurance funds spent to cover eventual failures - you'll understand). Life concerned industries already use such modular formal specification techniques.
In any cases, we see that even when such methods are not used automatically by the computer system, the programmer has to use them manually, by including the specification in comments or understanding the code, so he does computer work.
Now that you've settled the skeleton of your language's requirements, you can think about peripheral deduced problems.
.....
What is really useful is a higher-order grammar, that allows to manipulate
any kind of abstraction that does any kind of things at any level. We call
level 0 the lowest kind of computer abstraction (e.g. bits, bytes, system
words, or to idealize, natural integers). Level one is abstractions of these
objects (i.e. functions manipulating them). More generally, level n+1 is made
of abstractions of level n objects. We see that every level is a useful
abstraction as it allows to manipulate objects that would not be possible
to manipulate otherwise.
But why stop there ? Everytime we have a set of level, we can define a new level by having objects that arbitrarily manipulate any lower object (that's ordinals); so we have objects that manipulate arbitrary objects of finite level, etc. There is an unbounded infinity of abstraction levels. To have the full power of abstraction, we must allow the use of any such level; but why not allow manipulating such full-powered systems ? Any logical limit you put on the system may be reached one day, and this day, the system would become completely obsolete; that's why any system to last must potentially contain (not in a subsystem) any single feature that may be needed one day.
The solution is not to offer any bounded level of abstraction, but unlimited abstracting mechanisms; instead of offering only terminal operators (BASIC), or first level operators (C), or even finite-order offer combinators of arbitrary order.
offer a grammar with an embedding of itself as an object. Of course, a simple logical theorem says that there is no consistent internal way of saying that the manipulated object is indeed the system itself, and the system state will always be much more complicated than it allows the system to understand about itself; but the system implementation may be such that the manipulated object indeed is the system. This is having a deep model of the system inside itself; and this is quite useful and powerful. This is what I call a higher-order grammar -- a grammar defining a language able to talk about something it believes be itself. And this way only can full genericity be achieved: allowing absolutely anything that can be done about the system, from inside, or from outside (after abstracting the system itself).
.....
A
technique should be used when and only when it is best fit; any other use
may be expedient, but not quite useful.
Moreover, it is very hard to anticipate one's future needs; whatever you do, there will always be new cases you won't have.
lastly, it doesn't replace combinators And finally, as of the combinatorials allowed allowing local server objects to be saved by the client is hard to implement eficiently without the server becoming useless, or creating a security hole;
..... At best, your centralized code will provide not only the primitives you need, but also the combinators necessary; but then, your centralized code is a computing environment by itself, so why need the original computing environment ? there is obviously a problem somewhere; if one of the two computing environment was good, the other wouldn't be needed !!!; All these are problems with servers as much as with libraries.
Now that we saw how code reuse is possible, we are confronted with the main problem that arise from code reuse: unsecurity. Indeed, when you reuse some code that you did not write yourself (or that you wrote some long time ago), how can you be sure that this code works exactly as expected ? After all, perhaps there's some bug in the code, or worse, perhaps the code does work perfectly, but does not behave as it should to be a solution to the problem for which you use it. This problem is not specific to software written with reuse techniques; but what was a minor source of bugs without reuse becomes the one enemy of programmers now that reuse is possible, and that problems ever more complex thus can be (and are being) treated. So, the major problem now is being sure that some piece of code does fit the requirements for the program to work correctly. The first idea that arises is then "programming by contract", that is, every time some piece of code is called, it will first check all the assumptions made on the parameters, and when it returns, the caller will check that the result does fill all the requirements. This may seem simple, but implementing such technique is quite tricky: it means that checking the parameters and results is easy to do, and that you trust the checking code anyway; it also means that all the necessary information for proper checking is computed, which is loss of space, and that all kind of checking will take place, which is loss of time. The method is thus very costly, and what does it bring ? Well, the program will just detect failure and abort ! Sometimes aborting is ok, when you have time (and money) to call some maintenance service, but sometimes it is not: a plane, a train, a boat, or a spacecraft whose software fail will crash, collide, sink, explode, be lost, or whatever, and won't be able to wait for repairs before it's too late. And even when lives or billion dollars are not involved, any failure can be very costly, at least for the victim, who may be unable to work. That's why security is something important that any operating system should offer support for. Why integrate such support in the OS itself, and not on "higher layers" ? For the very same reasons that reuse had to be integrated to the OS: because else, you would have to use not the system, but a system built on top of it, with all the related problems, and you would have to rely on the double (or bigger multiple, in case of multiple intermediate layers) implementations, that each introduce unsecurity (perhaps even bugs), unadapted semantics, big loss in performance.
A structure A is interpreted in another structure B if you can map the symbols of A with combinations of symbols of B (with all the properties conserved). The simplest way to be interpreted is to be included.
A structure A is a specialization of a structure B if it has the same symbols, but you know more properties about the represented objects.
Imagine a real-time process is interrupted: will it continue where is stopped ? or will it skip what was done during the interruption ? Imagine the system runs out of memory ? Whose memory are you to reclaim back ? To the biggest process ? The smallest ? The oldest ? The first to ask for more ? If objects spawn, thus filling memory (or CPU), how to detect "the one" responsible and destroy it ?
If an object locks a common resource, and then is itself blocked by a failure or other unwilling latency, should this transaction be cancelled, so others can access the resource, or should all the system wait for that single transaction to end ?
As for implementation methods, you should always be aware that defining
all those abstraction as the abstractions they are rather than hand-coded
emulation for these allows better optimizations by the compiler, quicker
write phase for the programmer, neater semantics for the reader/reuser,
no implementation code propagation, etc.
Partial evaluation should also allow specialization of code that don't use all the language's powerful semantics, so that standalone code be produced without including the full range of heavy reflexive tools.
That is, without ADTs, and combinating ADTs, you spend most of your time
manually multiplexing. Without semantic reflexivity (higher order), you spend
most of your time manually interpreting runtime generated code or manually
compiling higher order code. Without logical specification, you spend most of
your time manually verifying. Without language reflexivity, you spend most of
your time building user interfaces. Without small grain, you spend most of
your time emulating simple objects with complex ones. Without persistence,
you spend most of your time writing disk I/O (or worse, net I/O) routines.
Without transactions, you spend most of your time locking files. Without
code generation from constraints, you spend most of your time writing
redundant functions that could have been deduced from the constraints.
To conclude, there are essentially two things we fight: lack of feature and power from software, and artificial barriers that misdesign of former software build between computer objects and others, computer objects and human beings, and human beings and other human beings.
To conclude, I'll say
Object vs. Project
Back to the Tunes Home page.
Page Maintainer: