Introduction

I describe software components.


I use diagrams where possible.


Box

Image

Fig. 1 Box

Box with Text

Image

Fig. 2 Box with Text

Component with Type

Image

Fig. 3 Component with Type

Component with Input Port

Image

Fig. 4 Component with Input Port




Relationships

Note that this diagram contains two boxes ("ty1" and "in").  



Intersection and size are the relations that determine that "in" is a subordinate of "ty1".


Green-ness determines that the box "in" is an "input".

Intersection

The box with name "in" intersects the box with name "ty1". 



Size

The box "in" is smaller than the other box.


Green-ness

Green-ness is my convention for "input".[1]



Component with Output Port

Image

Fig. 5 Component with Output Port




Yellow-ness

Yellow-ness is my convention for "output".

Component with 2 Input Ports and 3 Output Ports

Image

Fig. 6 Component with 2 Input Ports and 3 Output Ports

Two Components

Image

Fig. 7Two Components

Two Components 2 Connections

Image

Fig. <$n:figure:TwoComponentsAndTwo Connections>Two Components and Two Connections


NC (No Connection)

Ports that are not connected are termed NC (No Connection).


NC applies to input ports.


NC applies to output ports.


In Fig. <$n:figure:TwoComponentsAndTwo Connections>, ty1.in1 and ty1.in2 are NC inputs.


[Note that in mathematical notation there is no concept of NC, a function, once invoked, always returns a value (or an exception, or, …)].

Run Forever

Components run forever.

Concurrency

Components are concurrent.


Components run at their own speed.


[Components can be parallel.  Concurrency is a prerequisite of parallelism.[2]]

Aspects of Components

A Component has these distinct aspects:



Input ports names must be unique within the input port namespace.


Likewise, output ports names must be unique within the output port namespace.


Input port names can be the same as output port names (the input port namespace is distinct from the output port namespace).

Name Spaces

A Component has various distinct namespaces


[Question: What is the namespace for types?  IMO, types are other components (filter components — one in, one out).  A component includes "types" as children components.]

Signature

The signature of a component is defined by:


The signature of a Composite Component is extended to include:




Send()

A component can send an event to another component via a connection.


In fact, components cannot send events to their peers, they can only send events to their enclosing parent components.  The parent component routes the message send to another child.  The parent component routes the child's response back to the sender.  

Parameters

Parameters are subsumed by Send().


Parameters can be sent one-at-a-time or in a block.


In contrast, functional PLs require that all parameters be sent as a block, all at the same time.[3]


Time oriented processes can send information (parameters) at any time and are not restricted to sending such information in constrained blocks.

Return Values

Return values are subsumed by Send().


Components never "return".  They simply Send() information.

Exceptions

Exceptions are subsumed by Send().


Components never "throw exceptions".  They simply Send() information.  Sometimes, the information relates to error conditions.


NC - No Connection

The parent can choose to make any connection(s) NC — no connection — in which case:

Dependencies

There are no implicit dependencies between components.


All dependencies appear explicitly on the diagrams, e.g. as containment relationships and connections.

Inheritance

There is no inheritance in this system[4] of components.


Components are created by composition.


Children components cannot override the actions of parent components.[5]



Composition

Composite Components may include other components via composition.


Component Names

Components of the same type (within the same parent component) are given distinct names.


Often, though, names are not necessary, e.g. when there is but one component of a given type within a given parent.  In such cases, component names are optional.  In such cases, components can be referred to by their type name.


In Fig. <$n:figure:TwoComponentsAndTwo Connections>, there is but one component with type ty1 and one component with type ty2.  It is sufficient to refer to these components as ty1 and ty2, resp. (the components do not need names — the compiler might generate names for them, e.g. via gensym, their unique address, non-colliding hashcodes, URIs, etc.)



Busy()

Components are busy if they are doing something.


Notably, a composite component is busy if any of its children are busy.


A leaf component is busy if it is processing an input event.  This condition is never observed when the underlying hardware is stack-based (CALL/RETURN using a stack causes components to run to completion before executing a RETURN instruction) and all components reside in the same thread.

Ready

A component is ready if it has one or more events in its input queue and is not busy.

Event

Events contain two things:


The sender and the receiver must agree on the shape (aka type) of the data.


[Note that type checking is just another kind of component.  We could insert type-checking components in between two Components.  We could insert a chain of type checkers between two Components.  See the Examples section.]

Reaction

A component reacts to one — and only one — event at a time.

Run To Completion

A component processes one event to completion.


A component cannot process the next event until it has completed processing the current event.


Note that, a component can be broken up into a number of states.  A component might process an incoming event by simply changing its state.  This — breaking up a component's action — might be considered to be an idiom that subsumes the concept of loop (recursion).


See, also, Feedback.

Composite Components

Components can be composed of other components.


Composite components can be implemented recursively — i.e. a composite can contain other composites.


A composite component is implemented by appending two structures to the generalized concept of component:

Leaf Components

Leaf Components contain implementation (e.g. code) in some other notation (e.g. Python, Javascript, C, StateCharts, Drakon, etc.).


Leaf Components differ from Composite Components in that their implementation is not described by this notation.


Dispatcher

On stack-based hardware and operating systems, this system of components is "run" by using a dispatcher routine.


The dispatcher is a simple loop that runs forever and invokes components that are ready, in any order.


Macros / Shorthand

Repetitive diagram elements can be subsumed into (diagram) editor macros and shorthand gestures created can be created for them.


Furthermore, repetitive diagram elements can be elided, removing them from the diagrams.  The diagram compiler(s) re-insert such relationships in ways that are invisible to the Architect and to Engineers/Maintainers/Implementors.


For example, +V and GND (ground) appear on electronics schematics.  They are connected to every electronics component.  If every such connection is shown explicitly, then the diagrams become too busy to read.  Most schematic editors elide such details from the schematics.  Such information is not deleted, but only suppressed and elided from a given class of diagrams.

Diagram Compilers - Multiple

There is at least one compiler for each dialect of a diagram language.[6]

Component Constitution

Components are generally defined in a static manner with a static signature, e.g. {kind, inputs, outputs}.



Static Signature

Components are statically defined by:

Dynamic

At load-time, static components are instantiated (possibly many times).  Each instance contains several methods that apply only to the load-and-run-time instances, e.g. input queue operations, output queue operations, etc.


Load-time is distinct from run-time in that component instantiation occurs only during load time.


Instances, and routing information, cannot be changed at run-time.


Note that composite components are recursively instantiated to include an instance of all children parts along with an instance of routing information.  Part collections and routing information are completely private to the containing composite components [these are akin to local variables or hidden heap-based items in other languages.]

Implementation

As already mentioned, certain parts of components are instantiated at load-time, including:


The above are completely private to each instance.

Long Running Loops / Deep Recursion

Components must complete their actions "quickly".


Components process one input event at a time, i.e. components "run to completion".  


There is no requirement for preemption.




Feedback

Components can send messages to themselves, for example, to "continue" looping.

Model

Components model the real-world where each component gets its own CPU and memory.


Components are connected via thin pathways.



Memory Sharing

Memory sharing is not implicitly supported.


Memory sharing is an optimization applicable to only certain kinds of applications.


Memory sharing causes accidental complexity and complication.


Memory sharing should not be used in applications that do not need it.


Time Slicing

Time slicing is not implicitly supported.


Time slicing is an optimization applicable to only certain kinds of applications (e.g. operating systems).


Time slicing causes accidental complexity and complication.


Time slicing should not be used in applications that do not need it.


PU Not CPU

In fact, there is no such thing as a central processing unit.  


All processors are distributed.


I will try to use the term PU (processing unit) or just processor in place of the more common term CPU.


If I do use the term CPU, it should be taken to mean PU.

Multiple Cores

Multiple cores are just a half-hearted attempt at providing true distributed processing.


Multiple cores might also be considered to be an optimization of distributed processing, where the PUs are interconnected via high bandwith pathways and where the PUs can share memory.

Caching

Caching is an optimization.


Caching brings a lot of "baggage" — accidental complexity — with it.


Optimizations, such as caching, should be employed on a per-project basis and not be made generally available.  Caching should not be built into every PU nor built into every application.  IMO.

Kinds

Kind is like type, but I think of types as filters (other children components), so I use a word different from "type".  Data is passed through various kinds of filters that guarantee that their outputs contain a certain kinds of structure.  


Filters have a second output — failure (aka exception).  


Failure exceptions are connected in application-specific manners. 


Signature Compatibility - Referential Transparency

Two Components are equivalent if their static signatures are the same.

 

They are said to be pin compatible, or, referentially transparent.

Example - Type Checking Chain

bit —> collect into bytes —> collect into words —> convert to characters —> convert into objects —> check for certain fields —> convert to certain types


[1] It is possible to use shape to define input/output instead of color.  I have tried this and find that it makes the diagram "too busy" looking.  The choice really depends on the writer(s) and the reader(s) of the diagram and the display medium (I used only B&W displays in the early versions of these diagrams).   One could, also, use hidden attributes to store directional information, but I work with problems that make in-ness and out-ness important enough to be shown and not elided. In EE school, we were taught to use only one convention, since this makes diagrams accessible to a wider range of audiences (and concentrates/focusses the learning curve to a single course/curriculum). Mechanical Engineers provide 3 views of every drawing (front, side, top), but this may be reconsidered when using computers that can display 3D drawings.  One must remember that mathematics was developed as a notation for pen-and-paper mediums.  The 1.5D convention for writing computer code is derived from mathematics (most PLs use a 1.5D coordinate space to represent computer programs - columns of non-overlapping cells of characters arranged in rows of lines).

[2] See Rob Pike's "Concurrency is not Parallelism" in https://guitarvydas.github.io/2021/01/14/References.html

[3] FP eliminates time (t) as a variable.  This can be a useful approximation if used in moderation.  This kind of approximation is not suitable for functions that involve time, e.g. robotics, sequencers, Physics (see Nobel Laureate Ilya Prigogene's book "Order Out Of Chaos).  I have noticed that my operating systems (Linux, Mac, etc.) and editors (emacs, aquamacs) periodically "hang" due to timeless code being force-fitted into timeful functionality.

[4] Inheritance can be design-in explicitly using connections and containment.

[5] See StateCharts for a simple way to avoid overriding https://guitarvydas.github.io/2020/12/09/StateCharts.html

[6] For example: a component network compiler, a Statechart compiler (for implementation of some leaf components), a JavaScript compiler, etc.