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.

Some of code for this article is at http://github.com/mjburgess/FauxO


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,


require ‘time’
def get_user
“Michael is 24 on May 30th”
end
def days_till_birthday(data)
month, day = data.match(/\w+ is \d\d on (\w)+ (\d\d)/).captures
Date.parse(month + ‘ ‘ + day) - Date.today
end
print days_till_birthday(get_user), ‘ days until birthday’

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?

Well compare with,

# …
def extract_date_from_user(user)
user.match(/\w+ is \d\d on (\w)+ (\d\d)/).captures
end
def days_till_birthday(data)
month, day = extract_date_from_user(data)
Date.parse(month + ‘ ‘ + day) - Date.today
end
puts days_till_birthday(get_user).to_int.to_s + ‘ days until birthday’

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.today
end

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


def extract_date_from_user(user)
month, day = user.match(/\w+ is \d\d on (\w)+ (\d\d)/).captures
month + ‘ ’ + day
end
def diff_days(date)
Date.parse(date) - Date.today
end
puts diff_days(extract_date_from_user(get_user)).to_int.to_s + ‘ days until birthday’

This is closer to a “solved form” for total decoupling: we thread together the piece of our various problem-solving components only at the point we solve a particular problem.

Indeed there is a general structure to the class of such solutions: the queue.

Is this not equivalent,

require ‘time’
def get_user(id)
“Michael is 24 on May 30th”
end
def extract_date_from_user(user)
user.match(/\w+ is \d\d on (\w+) (\d\d)/).captures.join(‘ ‘)
end
def diff_days(date)
Date.parse(date) - Date.today
end
def print_message(days)
puts days.to_int.to_s + ‘ days until birthday’
end
[:get_user, :extract_date_from_user, :diff_days, :print_message].map { |s|
method(s)
}.inject(1) { |state, fn|
fn.call state
}

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,

  1. Unitary state transformation
  2. Co-unitary state transformation
  3. N-ary state transformation
  4. Unitary state transformation with dependencies
  5. 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.

The typical example would be validation,

#general form
def validate_x(value)
if value !~ /xyz/
raise “Invalid”
end
end
#general form
def get_x()
puts “Enter X”
gets
end
form_values = [:get_username, :get_password, :get_email]
validators = [:validate_username, :validate_password, :validate_email]

Supposing that each of those functions existed, we would require,

:form_value -> :validator -> :form_value -> …

This is (2).

As for the N-ary I suspect it’s sufficiently general to have,

:x -> m(:x, :y) -> :y

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 + y
end
def sub(x, y)
x - y
end
def data
(Random.rand * 100).to_i
end
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.

The commentary is in the notes next to the article body.


Stateful Queues (“Complects”)

# methods for these examples
def add(x, y)
x + y
end
def mul(x, y)
x * y
end
def addOne(x)
x + 1
end
def mulTwo(x)
x * 2
end

Dependent functions

dependent_a = Complect.new [:add, :mul], [5, 10]
dependent_b = Complect.new [:add, :mul], [1, 2]
puts ‘dA’
print ‘(((1 + 5) + 1) * 10) * 2 = ’
puts dependent_a.weave(1, dependent_b)
puts ‘dB’
print ‘(5 + 5) * 10 = ’
puts dependent_a.run(5)

Free (non-dependent) functions


free_a = Complect.new [:addOne, :addOne]
free_b = Complect.new [:mulTwo, :mulTwo]
puts ‘fA’
print ‘((5 * 2) + 1) * 2) + 1) = ’
puts free_b.weave(5, free_a)
puts ‘fB’
print ‘5 + 1 + 1 = ’
puts free_a.run(5)

Middlewear


puts ‘Weave middlewear’
print ‘ = ‘, free_b.weave_with(5, free_a) { |state, fn, _|
print “ (#{state}) #{fn.name}”
state
}

Output

dA
(((1 + 5) + 1) * 10) * 2 = 140
dB
(5 + 5) * 10 = 100
fA
((5 * 2) + 1) * 2) + 1) = 26
fB
5 + 1 + 1 = 7
Weave With
(5) addOne (6) mulTwo (12) addOne (13) mulTwo = 26

Stateless Queues (“Couples”)

Free


def data_producer
[‘Michael’, (Random.rand * 100).to_int]
end
def data_transformer(data)
{name: data[0], age: data[1]}
end
def data_consumer(data)
if data[:name].length > 0 && data[:age] > 1
puts data.inspect
else
puts “invalid format”
end
end

producer = Couple.new Array.new 5, :data_producer
consumer = Couple.new Array.new 5, :data_consumer
producer.weave_with(consumer) do |consumer, producer|
[[consumer], [->() { (data_transformer(producer.call)) }]]
end

Dependent


def input(message)
puts message
gets
end
def validate(data, regex)
puts data =~ regex ? ‘match’ : ‘fail’
end
questions = [‘Name?’, ‘Age?’, ‘Location?’]
questions = Couple.repeating :input, questions
validators = [/[A-Za-z]+/, /\d{1,3}/, /[A-Za-z]+/]
validators = Couple.repeating :validate, validators
questions.weave(validators)

Hybrid (technically Free)


def inputter(message)
->() {
puts message
gets
}
end
def validator(regex)
->(data) { puts data =~ regex ? ‘match’ : ‘fail’}
end

Couple.weave [inputter(‘Name?’), inputter(‘Age?’)],
[validator(/[A-Za-z]+/), validator(/\d\d/)]

Output

{:name=>”Michael”, :age=>87}
{:name=>”Michael”, :age=>85}
{:name=>”Michael”, :age=>67}
{:name=>”Michael”, :age=>72}
{:name=>”Michael”, :age=>57}
Name?
Michael
match
Age?
23
match
Location?
UK
match
Name?
23
fail
Age?
Michael
fail

Thus concludes the ‘ActionList’ aspect of my library, which here I’ve called a queue. (‘Action’ inspired from ‘IO actions’, etc. via Haskell).