Hybrid Programming and Paradigm Selection

20.1 Most production languages are multi-paradigm

C: imperative and procedural, with data abstraction conventions.

C++: procedural, object-oriented, generic, and functional techniques.

Java: class-based OOP, generics, lambdas, streams, and concurrency APIs.

C#: OOP, functional-style expressions, LINQ, async/await, and pattern matching.

JavaScript/TypeScript: imperative, functional, prototype-based OOP, event-driven, asynchronous, and reactive styles.

Python: procedural, OOP, functional techniques, generators, decorators, and asynchronous programming.

Rust: imperative control flow, algebraic data types, traits, ownership, and functional patterns.

Haskell: strongly functional with lazy evaluation and explicit effect modeling.

Prolog: logic programming.

SQL: declarative relational querying.

Erlang/Elixir: functional programming with actor-like concurrency through processes and message passing.

These summaries are broad; individual languages and runtimes evolve.

20.2 Choose a paradigm by problem characteristics

Approaches worth considering Step-by-step hardware control Stateful domain entities and contracts OOP, DDD, state machines Data transformations and business rules Data retrieval and filtering UI events and interaction I/O-heavy workloads CPU-heavy independent work Parallel/data-oriented techniques Distributed state ownership Actors/message passing High-throughput batch processing Data pipelines and data-oriented design

Explicit middleware or aspect-oriented techniques where suitable

20.3 Avoid paradigm dogmatism

Do not force every task into one style. A simple loop may be clearer than a complex chain of higher-order functions; an immutable transformation may be safer than shared mutable state; a SQL query may be better than loading all records into application memory. Choose the least complex approach that meets correctness, maintainability, performance, and team requirements.

20.4 Refactoring between paradigms

A safe refactor should:

Define observable behavior and invariants.

Add tests for normal, boundary, and failure cases.

Identify state, side effects, and dependencies.

Change structure in small steps.

Compare outputs and performance.

Review error semantics and ordering.

Keep or improve observability.

Document trade-offs and avoid unnecessary abstraction.

20.5 Anti-patterns

Inheritance used only for code reuse without substitutability

Giant “god objects” or global procedural modules Overly clever functional pipelines

Mutable shared state without clear ownership Unbounded async task creation

Event handlers with hidden side effects and no cleanup Declarative configuration that executes arbitrary code

Rule engines with undocumented precedence Generic abstractions that hide essential domain behavior

Premature data-oriented optimization without profiling Mixing business rules into UI rendering

Treating a paradigm as a substitute for testing or design

20.6 Decision record template

Problem:
Constraints:
Candidate approaches:
Chosen paradigm(s):
Why this fits:
Rejected alternatives and reasons:
State/effect strategy:
Concurrency model:
Performance evidence:
Testing approach:
Known limitations:
Revisit when:

Exercise

Select a paradigm for each of these tasks: calculator, compiler, bank ledger, responsive UI, scheduling engine, data pipeline, firmware driver, and collaborative real-time editor. Explain where a hybrid design is appropriate.

Quiz

? Which approach fits "relationship inference" best, according to the selection table?
+ Logic programming :: Facts and rules answer relationship queries.
- Data-oriented design :: That targets throughput and memory layout.
- Actors :: Those manage distributed state ownership.
- CSS :: Not a general-purpose approach.

? What is the first step of a safe refactor between paradigms?
+ Define observable behaviour and invariants. :: You cannot preserve what you have not defined.
- Rewrite everything in the new style. :: Change should be small and incremental.
- Remove the tests. :: Tests protect behaviour during refactoring.
- Measure performance only. :: Behaviour comes first.

? Which are listed anti-patterns? (select all that apply)
+ Inheritance used only for code reuse without substitutability :: Violates Liskov substitution.
+ Unbounded async task creation :: Exhausts resources.
+ Declarative configuration that executes arbitrary code :: Defeats validation.
- A simple loop instead of a complex higher-order chain :: The module recommends that when it is clearer.