5. Composable functions¶
Date: 2019-11-01
Status¶
Accepted
Context¶
We need a reasonable tradeoff between ease-of-use and maintainability.
Specifically, we need composable, combinable units that can be improved independently.
Approach 1: Classes + Methods¶
One approach is to embrace Object-Oriented Programming (OOP) with fluent interfaces (i.e. method chaining):
project
.create(...)
.update(...)
.delete(...)
Characteristics:
Ease-of-use is maximized, but this requires each method to
return self
.Also, this approach implies that if a function can be called with X different object types, each of those object types should have a corresponding method that applies that functionality and then
return self
.
How to enforce these characteristics?
Any solution will be a tax on maintainability, as code that adheres to these characteristics will include many non-semantic lines simply going through the motions of return self
and copying function usage into dedicated methods for each class.
Approach 2: Types + Functions¶
Another approach is to embrace a functional programming style: simple types and functions (no methods).
Usage is not as terse as for OOP:
p = tc.project.create(...)
u = tc.project.update(p, ...)
d = tc.project.delete(p, ...)
Characteristics:
Ease-of-use is not optimized, but still reasonable.
With tab-completion, ease-of-use is comparable to OOP.
Each type can be made immutable
Each function can be made pure
Functionality can be shared by calling the same function in user-land, not copying function calls in contributor-land.
Decision¶
Use @dataclass(frozen=True)
to model types and plain Python modules and functions to capture business logic.
Consequences¶
Immutable types and pure functions make the code much easier to reason about, drastically cutting down the time to ramp up and debug.
Functions are easily composable without accumulating undesired side-effects, unlike methods.
Note that not all types and functions have to be immutable and pure, but immutable types and pure functions should be the default.
If there are good reasons to make exceptions, we can do so, but we should include comments to explain why that exception was made.