Summary
What are the pros and cons of splitting pure functions into passive objects that describe the algorithms and active objects that can execute those algorithms? Note that the situation is greatly simplified by the fact that the functions have no side effects.
Detail
The portion of the code I’m writing (in Python 3) will largely adhere to functional programming.
There is some (immutable) data. There are some algorithms. And I need to apply those algorithms to the data, and get the result.
The algorithms could be represented as regular functions, which will be transformed using standard operations (e.g., I may compose two functions, then freeze some parameters using functools.partial, then passed the resulting function to another function as an argument). Many of the lower-level functions would be memoized for performance reasons.
But an idea occurred to me that perhaps I should instead represent algorithms as passive objects. Such objects wouldn’t be able to execute anything themselves. When I’m ready to execute, I’ll feed the algorithm object and all the inputs it expects into a special “computation” object. This would match my mental model of an algorithms far better, but I’m concerned that I might be missing some problems with this approach.
Algorithm objects could be implemented in a variety of ways; perhaps even multiple implementations could be allowed. Let’s say my algorithms are instances of an abstract class Algorithm; then its subclasses could represent:
- strings of text in a domain-specific language that I’ll create
- some kind of execution trees that I’ll construct
- even regular Python functions
I have never done this before, so I wanted to get some feedback on this idea. Does it offer any real design advantages, apart from my subjective feeling that it’s more “natural”? Does it lead to any problems?
I don’t think the design offers any major advantage or disadvantage.
Assuming that any computation object can run any Algorithm, then your class
Algorithmpresumably is going to have a function called something likeexecutethat knows how to run the algorithm. Name that function__call__, and now yourAlgorithmclass is exactly like a Python callable object (including functions).For your strings of DSL code: under your design you’d represent them as a subclass of
Algorithmthat overridesexecuteto run an interpreter. Under the other design you’d just do something like:And similar to create a function that when called will execute a specified expression tree.
Of course I might be missing something that you’re planning to put into your Algorithm design that’s not possible for functions. Not all Python functions have mutable attributes, for example. But since user-defined functions can be closures, can have attributes, and any object can “behave like a function” just by implementing
__call__, I suspect it’s different names for the same thing.Choosing your own names, of course, is a small advantage if it aids code readability. And it might feel a bit more natural to attach attributes to “objects” than it does to attach them to “functions”, if your computation objects are going to interrogate certain known attributes of Algorithms in order to help decide what to do when computing them (for example whether or not to memoize).