I'm developing a PHP library to work with big numbers (at this moment only with a "Decimal" class, but I want to add new classes, to handle Integers, Rationals, and Complex numbers). In any case, the problem is the following:
On one hand, PHP hasn't method overloading. I can use "magic methods", but I want to keep type hinting in method signatures. Even if I have a common interface, every number type deserves specific handling because its internals will be completely different, so finally it appears as a natural idea to add type-specific methods to allow interoperability between those classes (Decimal, Integer, Rational...).
On the other hand, there are virtually infinite functions that someone may want to apply to a number. Currently, I'm implementing many of such functions in every class to directly handle the internal properties and avoiding the creation and destruction of intermediate objects (a good example is the sin, exp or cos computing, because they have a lot of intermediate computations).
Then, keeping type specific methods increases the class complexity, and keeping the function methods in the number classes also increases the class complexity.
The problem is that I don't have any good idea about how to extract the function algorithms outside the "number classes" without loosing much performance and without violating the properties encapsulation.
Anyone knows a "good" pattern to follow?
Important to note: I think the problem is not trivial mainly because numbers aren't well handled in a hierarchical way, there are a lot of overlappings between number sets, and using interfaces isn't enough (see this question about "self-types" to see why i say that). I don't want a "perfect" solution, but one good enough that allows me to design a good library for theory purposes, not only for numerical computing.
The case: imagine a class that represents a number, call it Number
. Suppose that, in addition to basic arithmetic operations, you have a lot of one-variable functions: sin, cos, tan, arcsin, arccos, arctan, exp, logarithm, square root, gamma, riesz, airy's function, and so on... and add to it many other two-variable functions, and maybe even more complicated functions (with theoretical applications, not randomly defined) with 3 or more variables.
One possibility is to extract the algorithms to compute such functions to other classes in order to keep the class size under control. The problem of this approach is that then you have to expose the internals number classes if you want performance, or loosing a lot of performance if you want to respect SOLID.
I have three points that I want to control: classes size (to improve readability), SOLID principles (because the library has to grow a lot and ensure quality and testability), computation performance (because the library's purpose is to be useful, not a toy).
Proposed Idea:
Finally I've arrived to the conclusion that I should abstract the functions using specific (and instanciable) classes. This will allow me to implement new interesting features like function composition, second order functions, computing limits, integrals and differentiations in a symbolic way... That's a lot of work, but in first place I'll implement only the pillars to make it possible.
Functions will work in first place directly with number objects, and where it's worth I'll add a custom inner representation to boost the performance. Translating the object to the function's inner representation will add some overhead, but less than creating and destructing Number intermediate instances (or I think so, I'll benchmark this type of changes to ensure the complexity increase it's not a waste of time and code lines).
Since PHP don't have parametric classes, part of the contract will be checked at runtime with specific methods, if possible at "function-construction time" rather than at "function-evaluation time". For example, I'm thinking on adding two getters to obtain the return type and the argument "types" ¹ and n-arity. About the second mentioned getter, this will be only for "corner cases", since I prefer to subclass and add a specific and hinted method.
- Here I use the word types, but I'm referring to number sets, it's not the same even it seems so. The addition of Function classes and instances allow me to create type-specific methods without having to worry about class size (the number of methods will be small even if I have type-specific methods).