Functions have a number of properties that make them fantastic building blocks for code:
Encapsulation: functions hide their implementation and only expose their signature.
Simplicity: functions have a single responsibility and don't mix nouns with verbs, which makes them fundamentally untangled.
Stateless: functions are just code; they don't contain state or instance.
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.
So how would a simple function look as a Lego-like brick?
Let's take a look at a larger function and how function calls work within the block metaphor.
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).
It doesn't matter how large a function's implementation is, or how many other functions it depends upon, it only exposes one signature.
When a function call conforms to another function's signature then they can successfully "mate" creating a fulfilled function dependency.
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:
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.
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.