Skip to content

History of software composability

Alberto edited this page Oct 26, 2016 · 1 revision

A goal of computer science is the Establishment of an universal mathematical model for any kind of software. The main goal of software engineering is an high level language that could express user requirements and make them run. A language with full reusability and easy maintainability.

From the practical point of view, there is nothing more reusable than mathematical formulas and recipes.

Mathematical formulas include binary operations A -> A -> A. With these binary operators it is possible to combine any number of elements, two at a time.

From the point of view of computer science ideally the goal is an algebra for software components which may allow unrestricted composability.

In the other side, recipes are sequences of instructions easy to follow. When cooking a recipe, the task can be interrupted and restarted, it can be stopped and re-executed at a later time. If we receive a half cooked dish, we can continue a the point by following the rest of the recipe. We can perform a cooked recipe in a distributed way, among different individuals. If we fail at any step we can recover and restart the execution at a the most advanced point possible, leveraging the work already done.

From the mathematical point of view, recipes are monadic expression with effects of backtracking, checkpointing and resuming, continuation execution, event handling etc. The "horizontal" composability provided by algebraic expressions would be complemented by "vertical" composability in which a component (maybe an algebraic combination of components) produces a result the next step. This chain of steps is interpreted by an engine which navigate the monad to produce these effects depending on the internal or external events that happens during the execution. This interpreter is the monad itself.

The question is why software can not be expressed in clear formulas (algebra) and recipes (sequences of steps)?

The main obstacle is the absence of a way to express composability when events/callbacks, web requests , GUIs eevents, blocking IO calls, threading, remote messages, and long running loops are involved.

Such problem has a long history. In the early computers of the 50's. There was no way to compose file IO with CPU processing. Early computers had only batch processing where all file input was done at the beginning. Then a pure processing was done and all the output was produced at the end. All early computers were reactive for some definition of "reactive".

The time sharing Operating Systems of the 60s introduce synchronous blocking IO so that a file or console input could be managed programatically similarly to a function call. Really the OS interrupted the execution, waited for the buffer to be filled while doing other tasks and then resumed the execution.

That gave a form of limited recipe or monadic-like composability where the IO effect was possible. This created the console application, a new kind of application where IO and processing were freely mixed.

The price to pay was blocking: Each coding sequece would attend a single source of input at a time. This problem was more evident when the first GUIs needed to attend more than one source of input: the mouse.

The solution was double: On one side, multithreading allowed more than one thread per program, so one thread could block waiting for the keyboard, while other could wait for input from the mouse. The other model was a return to the state machine, or the event loop, where each source of input would send asynchronous messages. OS primitives like "select" of "epoll" in Unix allows such state machine model.

In both cases it is necessary a central state for coordination among threads and/or among event handling code. The software is no longer composable, not even in the weak sense of the console application, since the code is broken in different execution sequences that modify certain global state.

To ameliorate the problem of the state, some languages like Simula, intended for the simulation of discrete event systems, proposed the OOP model, where an object was a sort of small state machine, so the state was encapsulated by well defined interfaces so that the program could be done by connecting such objects.

Yet the software remain non composable until today.

Functional languages proposed a different solution to the event problem: If the OS continue the execution after an IO operation, some languages allows the programmer to handle his own continuations by means of special constructions. Such powerful mechanism allows to do whatever is necessary, at the cost of a contrived syntax which precludes composability.

Transient is a way to manage IO inputs by leveraging continuations and multithreading in a way that the execution is neither broken in disjoint event handlers neither the execution blocks, so that asynchronous and blocking IO operations can be composed algebraically in combinations of parallel and concurrent combinations so that the "recipe" is not broken.