GOTO

CPS — continuation passing style — is just a generalization of GOTO.

First-class functions, too, are just generalizations of GOTOs.

GOTO changes the program flow.

First-class functions, hence, CPS, allow one to pass program flow around as first-class objects.

One can call a first-class function object, hence invoke a continuation via CPS, at any point, well-away from its origin.

In FORTRAN, there was a construct called a “computed GOTO”. CPS is even more “powerful” than this FORTRAN concept.

Spaghetti Flow

CPS (and first-class functions) can lead to spaghetti coding, much like GOTO did.

Self-disciplined assembler programmers produced well-structured code in assembler.

Average programmers needed “help” from Structured Programming languages.

Self-disciplined assembler programmers complained about Structured Programming languages.

No one bothered to write code in assembler — structured or spaghetti — after the self-disciplined assembler programmers retired.

Self-disciplined FP programmers can produce well-structured code.

Uses for CPS

CPS is a low-level operation, like GOTO.

As such, CPS is useful in building control-flow constructs.

CPS is used in Denotational Semantics — which is a method for building languages and control-flow constructs.

CPS should not be used for regular programming.

Unstructured used of first-class functions should not be used for regular programming, either.

Threads

When FP breaks out of its sweet-spot, we have to resort to constructs like threads.

Threads are envelopes that allow the calculator-like paradigm to continue to be used — each calculator being wrapped in its own envelope VM.

Threads are based on state. Thread envelopes hide this state, but do not eliminate state.

In some use-cases,1 this kind of state is important enough that it should not be hidden from view.

It is possible to extend the FP paradigm beyond its sweet-spot, but the question that needs to be asked is whether this is a good idea.

The Questions

The questions to be asked are:

[The question is not: “how do we paper-over the FP paradigm, once we have found a fault in it?”]

The Fix

Structured Scaffolds

GOTOs were “fixed” by creating structured scaffolding around them.

FP creates structured scaffolding around constructs using its strict one-in-one-out rule (function-call-and-function-return (I won’t mention exceptions, which are based on a dynamic global variable2)). The FP paradigm is useful in one-in-one-out situations — e.g. fancy calculators3.

More Quesions

State

Is state The Problem?

Or, is unstructured use of state a problem?

State can be easily structured using StateCharts: https://guitarvydas.github.io/2021/02/25/statecharts-(again).html https://guitarvydas.github.io/2020/12/09/StateCharts.html

Global Variables

Were global variables The Problem?

Or was unstructured use of variables a problem?

The solution was to invent scoped variables, and, local variables.

This implies that global variables were not actually the problem, but that unstructured use of variables was the problem.

Locality

Lambda calculus gives us free variables.

At what point do free variables become global variables?

When a closure fits on one line, the variables don’t appear to be global.

When a closure fits on one window, the variables don’t appear to be global.

When a closure is large and doesn’t fit into one viewport of one window, the free variables appear to be like global variables.

It appears, to me, that locality is more important than global-ness. I could use4 a mini-language that had no local variables — and allowed only 26 variables at maximum. As long as lumps of that kind of code were well-isolated5, the global-ness of the variables did not bother me.

Currently, types and functions are in a flat space and are not localized / structured. Packaging systems are bandaids for this broader problem (of locality).

Q: How does one Structure the use of types? How does one Structure the use of functions? Without causing new/hidden dependencies? Is packaging enough or is it a bandaid?

Inheritance

Inheritance breaks locality, much like CPS does.

It is possible to invoke a method well-away from the original source point.

Inheritance for structuring code leads to debugging problems. One finds it hard to understand which methods can be overridden and where they are.

Composition exhibits less of this kind of problem.

Q: Can inheritance be cleaved into two pieces? The “good” inheritance vs. the “bad” kind?

Q: What is code reuse? A: An implementation detail.

Q: What is architectural reuse?6

Calculators and Desktops

Calculators are like desktops — old-fashioned concepts that act like bridges into new paradigms.

[It is my understanding that electric motors were first used to pump water uphill to create artificial streams that could power water wheels for mills and factories.]

Multiple Paradigms for Each Solution

I believe that it is important to use paradigms that fit the problem. I believe in using multiple paradigms to solve any one problem. IMO, force-fitting the same paradigm onto all problems is old-fashioned thinking. I believe that programming can break through its asymptote only through the development of muti-paradigm workflows.

[FYI - I, also, believe that diagrams should be raised to the same level of syntax as text. Diagrams show nesting in ways that is harder-to-read in text.]


  1. e.g. sequencers, DAWs, machine control, robots  ↩︎

  2. https://guitarvydas.github.io/2021/02/25/The–Stack–is–a–Global–Variable–(again).html  ↩︎

  3. e.g. ballistics calculations  ↩︎

  4. and, once, implemented  ↩︎

  5. https://guitarvydas.github.io/2020/12/09/Isolation.html  ↩︎

  6. https://guitarvydas.github.io/2020/12/09/Reuse.html  ↩︎