Foundations

Last updated 2 months ago

The metaphor is built upon two fundamental functional concepts: functions and namespaces.

These foundational concepts should be especially useful for people who are new to functional programming. If you already know your functions from your namespaces, then feel free to skim quickly through this section.

Functions

Functions have a number of properties that make them fantastic building blocks for code:

  1. Encapsulation: functions hide their implementation and only expose their signature.

  2. Simplicity: functions have a single responsibility and don't mix nouns with verbs, which makes them fundamentally untangled.

  3. Stateless: functions are just code; they don't contain state or instance.

  4. Purity: functions can be pure (i.e. have no side-effects) which makes them easy to understand, reuse, test and parallelise.

These properties make functions (especially pure functions) inherently composable and testable units of code. And it's why we chose to use Lego-like bricks in our metaphor, because Lego is an inherently composable building block.

All the code examples are written in Clojure. If you haven't read any Clojure code before then we recommend that you take a look through this fantastic five minute introduction.

So how would a simple function look as a Lego-like brick?

'square' multiplies its argument by itself and returns the result
The green body of the brick represents the function's implementation
The red stud represents a dependency on another function (in this case 'multiply')
The square-shaped hole on the bottom represents the function's signature

Signature:

The name, arguments and returned result of a function

Combining functions

Let's take a look at a larger function and how function calls work within the block metaphor.

'circle-area' calculates the area of a circle, given its radius
The green body of the brick represents the function's implementation
The yellow stud represents a dependency on our 'square' function

Notice that the shape and colour of the yellow dependency stud matches the shape and colour of square's signature hole.

The orange stud represents a dependency on the Math/PI constant and the red stud represents a dependency on the multiply function (notice that it matches the shape and colour of the mulitply dependency in the square function, that's because it's the same dependency).

The circle-shaped hole on the bottom represents the function's signature

It doesn't matter how large a function's implementation is, or how many other functions it depends upon, it only exposes one signature.

That's true, except for when it's a multi-arity function, but let's ignore that detail for now.

When a function call conforms to another function's signature then they can successfully "mate" creating a fulfilled function dependency.

'square' mated with 'circle-area'

Namespaces

We're using namespace as a common term for the concept of packaging related functionality together and giving the package a name. In Java this concept is called a package, in other languages it's called a module.

Let's see how functions are packaged into namespaces in Clojure:

Four related functions which can be packaged into a single entity
Collecting the functions together in a 'math' namespace

Now we can think about the math namespace as a single entity with a descriptive name and a collection of signatures. This makes it easier for us to find related functions and gives our codebase a navigable structure. Instead of keeping all our small blocks separately, we've organised them into larger blocks and given those blocks names.

In Object Oriented languages we have another entity to structure our code: the class. One package can contain several classes, each of which can contain several methods. Again, classes help us to divide up our codebase into understandable units of code. But note that classes work at the level of abstraction beneath a package.

Neither packages or classes give us a system level building block.

The missing piece

What if there was a way to structure several namespaces into a single entity, which exposes one interface and hides all of its implementation details? Then we'd have a system level building block that's as encapsulated and composable as a function.

Polylith calls this building block a component.