Functions as Objects
How to wrap logic to understandability and testability
Logic is the foundation of software. However, simply accumulating logic isn't effective. After just a few lines, logic becomes extremely difficult to understand, manage, and reason about. Manual testing by launching programs and triggering behaviors doesn't scale well.
Therefore, we need methods to package logic into smaller, more comprehensible units. We also need ways to apply targeted tests to specific portions of the overall logic.
How can we achieve this?
Returning to the scenario from my previous article introducing the concept of logic, how do we create "a network of objects" communicating through messages, as Alan Kay envisioned?
Here's an example of an object according to Radical Object-Orientation:
This object contains logic that creates its behavior. It has "pathways" for incoming and outgoing messages, with a boundary — a "membrane," if you will — separating it from other objects.
But how do we translate these "pathways" and boundaries into code? They can't be logic themselves, as they aren't active elements. They're structural components, requiring something different.
Fortunately, the answer is straightforward — though it might not initially seem object-oriented: functions.
Radical Object-Orientation doesn't immediately require classes (or modules more generally). To fulfill Alan Kay's biological cell analogy, it's sufficient to encapsulate logic in functions and connect them "in a special way."
By encapsulating logic in a function, we gain "a handle on it": a name. This is the minimum we can do:
This allows us to represent complex logic concisely:
The name and curly braces form the boundary around the logic, transforming it into a "building block" (or object) that can be "woven together" with others.
However, the "pathways" (arrows) currently seem useless. There's nothing to connect this single object to, and the logic remains difficult to test because it interacts with the environment and produces side effects.
No data flows into the object, no data flows out — instead, data flows from the console and back to it, which is part of the environment.
While we can test this logic by calling the function, passing text and verifying its categorization remains challenging. We'd need to redirect stdin/stdout.
What's the solution? Whatever is difficult to test can be separated into its own objects — or functions.
You would have done this immediately, I'm sure. It's obvious and straightforward. I delayed this step deliberately, though, to develop Radical Object-Orientation incrementally.
Of these three functions, two remain difficult to test: those accessing resources. However, the domain logic has been extracted and is directly accessible for testing without side effects. It's a pure function, making it easy to test.
Simultaneously, these functions can be viewed as objects woven into a message flow:
Now it becomes clear how the "pathways" in an object diagram are encoded: they represent function parameters and return values:
In Radical Object-Orientation, messages are the data passed into and returned from functions. And by preventing functions from calling each other directly, we respect the Principle of Mutual Oblivion (PoMO).
None of these functions know about each other. Each focuses solely on its job, with logic independent of others. Functions simply process received messages (parameters) and produce result messages, occasionally accessing environmental state or resources.
The message flow between objects is easily represented as a sequence of function calls:
This approach is highly comprehensible, providing an immediate overview of how behavior emerges. Logic isn't nested in deep call hierarchies but arranged sequentially through function calls.
I prefer this to nested calls like this:
Reading right-to-left is less intuitive than top-to-bottom. Saving two temporary identifiers doesn't justify reduced comprehensibility. More compact code isn't necessarily elegant — it often requires more mental effort to understand.
As you can see, functions are the new objects — almost. At least initially. They're the simplest means to implement Alan Kay's vision, particularly the messaging aspect, which is most crucial.
However, not just any function aligns with this approach! Functions must be constructed carefully, maintaining functional independence and conforming to the PoMO!
Plus, they must also follow the IOSP, which I'll explore in a future article.
And don’t worry, classes will be part of the implementation picture, too. Stay tuned!















