Perhaps the fourth concept is “event” giving us:
Precondition <-> Postcondition, the relationship between a condition before & after a computation.
Event <-> side effect, the relationship between the environment activating a computational change, vs a computation activating an environment change.
Precondition <-> Event, the relationship of an environmental change, triggering a computational change.
Side effect <-> Post-condition, the relationship between a computational change, triggering an environmental change.
—-
If we think of the loop as both initiated and consumed by the environment, OR by the computation, we get dual loops through all four steps:
We have environment changes interpreted as an event by computation, which responds with an appropriate side effect, which in turn changes the environment. Environment to environment, through computation.
Or the loop can start with a side effect, causing an environmental change, whose event lets the computation react to the environments reaction to the initial side effect. Computation to computation, through envirinment.
Identifying a duality means that there might be some opportunity to transfer or unify concepts, but they might not be strong enough, or the duality isn’t pure enough to be very useful. The obvious dual of a precondition is not effect, but postcondition (like require/provide, import/export, in/out, etc…). So I’m having a hard time seeing the fruit of calling precondition and effect a dual.