From OOP Interfaces to Functional Modules: A Journey to Simplicity and Composability

From OOP Interfaces to Functional Modules: A Journey to Simplicity and Composability


Introduction

If you've spent years in object-oriented programming, you're used to defining interfaces and switching implementations through subclassing or dependency injection frameworks. This model works, but when transitioning to a functional programming language like Scala, you might ask:

"Why abandon interfaces? Aren't they good enough?"

In this article, we’ll walk through a common OOP scenario, then gradually morph it into a purely functional approach using Scala 3 — without relying on subtyping. Along the way, we’ll highlight why this shift is not just syntactic, but also deeply empowering.


The Classic OOP Style (Java)

Let’s consider a simple logging service:

Article content
This is familiar and straightforward. We define an interface, create multiple implementations, and inject the one we want.


The Functional Module Pattern (Scala 3)

We now reimagine the same logic in a purely functional way — no interfaces, no subtyping, just values and functions.

Article content

Wait, Isn’t That Just the Same?

Functionally yes — both approaches provide abstraction and pluggability. But there are key differences once we zoom out to real-world systems.

Let’s break it down.


Why Functional Modules Are Better (in FP World)

1. They’re First-Class Values

You can pass them around, transform them, or dynamically create them.

Article content

2. No Type Hierarchies

Avoid the diamond problem, abstract classes, and tight coupling. You don’t need sealed, final, or marker traits.

3. Transparent & Composable

Modules can be composed naturally:

Article content

4. Simplified Testing

Creating test doubles is trivial:

Article content

5. No Runtime Dispatch

All dispatch is structural, not nominal — it’s based on the fields in the record, not class inheritance.

6. Plays Nicely with Effects Systems

Article content
This allows for seamless integration with cats-effect, ZIO, ReaderT, and more.


Conclusion: Familiar Pattern, Better Foundations

Yes, functional modules and OOP interfaces often look similar on the surface. But underneath, the functional approach provides more flexibility, composability, and reasoning power.

You’re not giving up abstraction — you’re gaining value-level composition, structural clarity, and a world where dependencies are data, not magic.

Make the shift — your code (and future you) will thank you.

To view or add a comment, sign in

More articles by Eli Golin

Insights from the community

Others also viewed

Explore topics