Flow-Design: OOP component programming

H. S. Teoh hsteoh at quickfur.ath.cx
Wed Feb 14 19:53:31 UTC 2018


On Wed, Feb 14, 2018 at 06:43:34PM +0000, Mark via Digitalmars-d wrote:
> On Wednesday, 14 February 2018 at 09:39:20 UTC, Luís Marques wrote:
> > It seems that someone once again rediscovered the benefits of
> > component programming, in the context of OOP, but (as usual) without
> > the more mathematical and principled approach of something like
> > ranges and algorithms:
> > 
> > [...]
> 
> Luna [1], a new programming language that was recently mentioned on
> Reddit, also appears to take this "flow-oriented design" approach.
> It's purely functional, not OO, and I'm curious to see how it evolves.
[...]

This so-called "flow-oriented design" is hardly a new idea.  It has been
around since Lisp and the Unix command-line. And of course, D ranges
benefit greatly from it. :-)

The power of this idiom comes from the way it confers symmetry of API
across diverse components.

In the traditional situation, if a library X exports methods foo() and
goo(), and another library Y exports methods bar() and car(), then you
cannot simply plug Y in where X is used, because their APIs are
incompatible, and you need to write proxying code to bridge the "API
impedance mismatch" between them.  For sufficiently complex APIs, this
is highly non-trivial, and often leads to a lot of boilerplate like
proxy objects, shim code, and other such artifacts.

For example, if X calls Y and Y calls Z, and X, Y and Z all have
different APIs, then removing Y from between X and Z will require
rewriting parts of X so that it will fit with Z's API.  And often this
may not even be possible without essentially rewriting the entire
subsystem.

However, under the component programming model, both X and Y would be
unified under a single, universal API: input and output.  This makes
their APIs symmetric: they become interchangeable with each other, from
the API point of view.  Now you can insert/remove components in the
middle of a pipeline without needing to rewrite everything around them,
because the symmetry of the API ensures that the new component(s) will
simply "just fit", no matter what order you attach them.

Of course, the limitation of this approach is that it only applies to
problems that are amenable to be modelled as a linear pipeline.  For
more complex problems that require non-linear algorithms, it will be
necessary to use some other kind of API to accomplish what's needed.
Though, the linearizable pieces of such algorithms can still benefit
from the component / pipeline approach.

But even in non-linear problems, if you can boil down the problem to its
barest essentials and unify the APIs of the relevant modules that way,
then you can still reap the benefits of having an API that's symmetric
across different modules.  One example of this is user-defined numerical
types: in languages that support operator overloading, the expression
syntax serves as the unifying "API"; as long as your custom numeric type
conforms to this API (i.e., it is symmetric w.r.t expression syntax),
you can simply just "plug it in" and it will Just Work(tm), for the most
part (complications like floating-point idiosyncrasies aside).

Finding this sort of symmetry in API design is a hard problem in
general, though.  While almost everyone can agree on "input + output" in
the pipeline model, a universal API that encapsulates more complex tasks
may not be so obvious. And as long as you can't get everyone to agree to
it, it will be difficult to interoperate the respective modules.  This
is why API design is very hard. :-)


T

-- 
"Real programmers can write assembly code in any language. :-)" -- Larry Wall


More information about the Digitalmars-d mailing list