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.).
This seems like a necessary and obvious state of affairs to most 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
puts days_till_birthday(get_user).to_int + ‘ days until birthday’
We have a function which provides us with some data in a format what we’d really like to know is a difference in two dates about data and then we print it out.
There is less coupling here than there might be, the obvious kind would be if days_till_birthday called get_user itself: this would be the most pernicious kind as it amounts to rendering the conceptual utility of separating code into function rather ineffectual in the first place if a function name is just a “jump here now” command.
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, so reciving some of the benefits of decoupling: isolatable testing, conceptual clarity, ease of modification, ease of reasoning, etc. But we still arent there yet. Rember 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. Have we not simply done,
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).
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 threads together: two queues of functions passing control back and forth. The typical example may 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 function 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 using queues).
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 = Couple.repeating :input, [‘Name?’, ‘Age?’, ‘Location?’]
validators = Couple.repeating :validate, [/[A-Za-z]+/, /\d{1,3}/, /[A-Za-z]+/]
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).