Post #105

The Three-Things Theorem of Function Intention

Functions should coordinate, calculate, or control

orizuru: origami crane

Origami, the ancient art of paper folding, has gone through a modern mathematical renaissance. The folding process was originally passed down between generations orally. Recently, though, artists have developed a way to document the steps to fold a shape with a diagram called a crease pattern.

two colorability of bird base

New theorems have emerged to determine whether a crease pattern can be folded. For example, a crease pattern must be two-colorable. Each region in the crease pattern can be colored with one of two colors in such a way that no two regions of the same color share a dividing crease.

The art of origami has been reduced to simple, elegant, binary theorems that represents its integrity.

A Theorem Proving Code Intention

We can think of our source files as crease patterns used to diagram the intention of the systems we build. We can then define theorems to help us ensure that our diagrams, our source files, have integrity. Each function should have a clear intention, and to prove that, I'd like to introduce The Three-Things Theorem of Function Intention.

A function should do one of these three things

Coordinate

Coordinate the actions of other small, well-named units by using a defined control flow mechanism.

function coordinator () {
  return callFirstFunction
    .then(res => callSecondFunction(res))
    .then(res => callThirdFunction(res))
}

A coordinating function is responsible for creating a pipeline of actions, for calling other well named functions. It manages interactions and that's it. Just like a good people manager, a coordinating function becomes less effective when it's splitting time actually doing the work that should be a delegated task.

Calculate

Calculate a very specific value, state, or object in our system.

function simpleCalculator (val1, val2, val3) {
  return Math.pow(Math.abs(val1 + val2) * val3, 2)
}

function domainObjectCalculator (input) {
  return { output: _.get(input, 'foo.bar.baz')  }
}

Calculating a value doesn't just need to be limited to a mathematical equation. A calculating function can also be used for transitions in domain concepts, like when we "calculate" new objects in our system.

Control

Encapsulate and expose ways to control the flow of our application.

function until (condition, run) {
  return function method (subject) {
    if (!condition(subject)) {
      return method(run(subject))
    } else {
      return subject
    }
  }
}

function nComplete(n) {
  return ...
}

function calculateNewN(oldN) {
  return ...
}

until(nComplete, calculateNewN)(5)

We can build functions that perform the control flow through our applications. They can hold conditional logic, looping, and anything algorithmic. This allows our domain functions to focus on performing a specific task. until is an example of an fairly complex path through our application, but structuring it as a higher-order-function allows you to prove that it works with tests that aren't mixed up by other complex domain logic. All the pieces can be tested in isolation, making them easier to develop, change, and understand.

Lean More

About this idea in a talk about code design that Sam gave called JavaScript Is Too Convenient

About origami from an amazing talk given by Robby Kraft on software built for origami artists


Sam's original post

Test Double helps software
teams scale with stability.