A central issue to building controllers, instead of calculators, is the number of outputs that can be produced a single input.
Most PLs (Programming Language) encourage the use of functions. Functions have a strict one-in-one-out policy. The function signature specifies the input and output APIs of the function. For example:
f(x) returns y throws e
defines a function which takes one input parameter and almost-always returns one output datum to the caller. Sometimes, the function returns an exception datum. The receiver of the exception is usually dependent on the dynamic[1] call-chain.
Some PLs relax the strict one-in-one-out rule by silently ignoring the return value.
NC is not the same as returning void.
NC is determined by the user of the component. The function signature might declare the existence of a returned value(s), but only the user determines if that returned value is used.
Ignoring the result is a special case of a more general concept.
A common idiom in EE is the specification of NC - No Connection.
Inputs can be NC.
Outputs can be NC.
Typically, a software function is defined by its signature.
I believe that each call-point should include a signature of how a component is intended to be used.
I believe that each call-point should include a signature of how a component is intended to be used.
A loader might "match up" signatures and complete type-checking before running an application.
Interfaces are meant to provide a way to define multiple signatures for a software component.
The concept of interfaces does not address the problem in full:
functions are specified by
https://en.wikipedia.org/wiki/Duck_typing
Duck typing is an attempt to provide calling signatures.
I believe that type signatures should be checked in phases.
For example,
Intermediate object files could be used for carrying partially-checked types, along with code.
DLLs address the calling signature problem in an epicyclic manner - they solve only part of the larger problem.
The larger problem is that of snipping all dependencies and to use later passes to fill in the details (this could be done in a layered manner instead of doing all of the work in one fell swoop).
Anecdote:
In the late 1980's, I worked with the Eiffel programming language.
I noticed that compile-times were non-linear for large systems. [Non-linear and becoming larger with each additional class. Exponential?]
The problem was that many classes were dependent on other classes. The type checker would check - and re-check - other classes while checking the validity of a unit being compiled.
I wrote an Eiffel type-checker in eLisp.
The new type-checker snipped inter-class dependencies and would generate intermediate object files that contained partial type information.
The final system-wide type check was performed by a loader that understood the partial type information stored in the intermediate object files.
[1] Dynamic is equated with "bad" IMO. Ask any Maintenance Engineer if it is easy to debug a dynamic system. Dynamic reconfiguration used to be called "self modifying code".