Braids, Weaves and the Complecting Problem

A strategy for decoupling

The refrain, “ loose coupling, high cohesion ” speaks to two kinds of complexity. In the case of coupling the “logical complexity” of the code: the degree to which its components are interdependent. In the case of cohesion the “conceptual complexity” (of the problem domain): how interdependent representations are simplified. This article will sketch out a library for treating coupling as a general problem.

The conceptual metaphor I will begin with - and all programming articles must have one - is that of braiding together various strands. In Gary Bernhardt’s re-envisioning of the “Boundaries” between layers of programming he proposes moving away from an inherently intertwined paradigm of programming, where each layer is necessarily aware of how it couples to the other layers: its return values, its argument prototype perhaps (gasp!) its logic itself is essentially contingent on how the rest of the application is structured. He proposes that “layering” becomes a matter not of domain logic, but of programming logic. The boundaries reflect what each component is doing (accessing state, pure computation, etc.) rather than what it means (calculate fuel costs, open door, etc.).

The reverse seems like a necessary and obvious state of affairs to some programmers,

We have a function which provides us with some user data in a given format and function parsing that format for information. What we’d really like to know, however, is a difference in two dates and the rest is details.

There is less coupling here than there might be, the obvious increase would be if :days_till_birthday called :get_user itself, this would be the most pernicious kind. This would render the conceptual utility of separating code into function rather ineffectual.

If not the most obvious kind of coupling (calling co-dependene), what kind of coupling is there?

Here we add a level of indirection (:extract_date…), so receiving some of the benefits of decoupling: isolatable testing, conceptual clarity, ease of modification, ease of reasoning, etc. But we still arent there yet.

Remember the question we’re really asking from a logical point of view is about the difference between two dates, indeed we might even have found a library function of the kind below to do this:

def diff_days(date) Date.parse(date) - Date.todayend

Why write code so totally contingent on the rest of the logic of the program (days_till_birthday) when the problem is totally general?

That is, in the code example above there is an implicit queue of function calls each passing their return value along to the next. The function Enumerator#inject serves this purpose (threading a value) in this rewrite.

The benefits of the kind of decoupling should be clear: each unit of the program it totally cohesive (for free!) — it is responsible only for the smallest and clearest piece of logic which makes sense the “logic of the problem domain” in its entirety is captured in the queue not in the functions! The functions could appear in any application.

Add to this the standard benefits of decoupling: isolation, testing, composition, reasoning, etc.

The Forms of Coupling

There are various “solved forms” for coupling-together independent functions,

Unitary state transformation

Co-unitary state transformation

N-ary state transformation

Unitary state transformation with dependencies

Co-Unitary state transformation with dependencies

(1) is the case above, the functions involved each have a single “gap” which can be used to thread values through a -> b -> c which has the effect of a variable changing state (x = a(0); x = b(x); x = c(x) ).

Since this single gap can be filled by an object (or any complex value), it is sufficient to generally describe any state dependence whatsoever. We require only a single gap to do general threading.

Co-unitary state transformation is what I have called weaving two such threads together: two queues of functions passing control back and forth.

that is, to provide a function m(left, right) which can wrap the subsequent function calls as much as it would like creating a “middle wear” strand throughout the entire “weave”.

Finally in of these cases we have the potential that our functions are of multiple parameters where the previous function in the chain should not simply “provide the rest”. Functions in the chain should not be designed to be in any particular chain: this is as bad as the original case (indeed it is the corresponding form of the problem).

So for example,

def add(x, y) x + yend

def sub(x, y) x - yend

def data (Random.rand * 100).to_iend

operations = [:add, :sub, :add]op_deps = [1, 2, 10]

input = [:data, :data, :data]

:data provides only one piece of input for the math functions, op_deps should provide the other. This amounts to [:add_one, :sub_two, :add_ten] which we could have written as wrappers over the originals.

Occasionally such wrappers (which I call “grafts” in my library) will be needed, adapters which turn completely general logic-focused units into problem-domain units.

However for many cases providing a ‘dependencies’ array to go along with your operational queue will be much better than adapters.

A Library for Coupling

And so, I shall now introduce my library. I’ll merely provide some code examples and some commentary on terminology and linking it to the examples above — in the hopes that the prior discussion was preparation enough.