Hi Alex and Giles, Thanks for the feedback.
1) Backward Compatibility and Complex Interface Yes. I understand the backward compatibility requirement and my goal is to be fully backward compatible. Fortunately, the existing Complex class has private constructors and so it is possible to refactor it as an interface. I was able to make the change along with a ComplexCartesianImpl class and run all unit tests successfully. I did not have to make any changes to unit tests. "mvn test" runs successfully on my local machine after the changes https://github.com/sumanth-rajkumar/commons-numbers/blob/sumanth-gsoc-22/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java https://github.com/sumanth-rajkumar/commons-numbers/blob/sumanth-gsoc-22/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexCartesianImpl.java This I assume should meet the backward compatibility requirements? The proposed functional interface changes introduces a new interface ComplexNumber. I think we could reuse the refactored Complex interface instead of a new ComplexNumber interface and still maintain full backward compatibility. This would provide flexibility to older applications to work with new implementations of Complex such as MutableComplexImpl or ComplexPolarImpl or even ComplexStructImpl in the future whenever Java supports value types. Please let me know what you think. 2) ComplexFunction and ComplexResult functional interfaces Yes the generic <R> type introduces noise and can be solved as you suggested. I was also thinking about an alternative that avoids the ComplexResult and the generic type <R>. We could split complex unary operators into two primitive functions ( ToDoubleFunction<Complex>) one returning the real part of result and other for imaginary part interface ComplexFunction { ToDoubleFunction<Complex> getReal() ; ToDoubleFunction<Complex> getImaginary() ; } And for example the Conjugate implementation would look like this ComplexFunction conj = new ComplexFunction2() { @Override public ToDoubleFunction<Complex> getReal() { return complex -> complex.real(); } @Override public ToDoubleFunction<Complex> getImaginary() { return complex -> -complex.imag(); } }; }; And the functions used like below in Complex and ComplexList Complex { // default implementation are immutable always returning new instance to maintain b/w compatibility default Complex applyFunction(ComplexFunction function) { return Complex.ofCartesian(function.getReal().applyAsDouble(this),function.getImaginary().applyAsDouble(this)); } } } ComplexList { .. // applies changes in place void forEach(ComplexFunction fun) { ToDoubleFunction<Complex> realFunc = fun.getReal(); ToDoubleFunction<Complex> imgFunc = fun.getImaginary(); ComplexCursor cursor = new ComplexCursor(); while (cursor.index < r.length) { cursor.apply(realFunc.applyAsDouble(cursor), imgFunc .applyAsDouble(cursor)); cursor.index++; } } ... } Does this make sense or we just stick to the original interface that includes ComplexResult<R>? 3) Naming convention for the Functional Interfaces On reviewing the functions in java.util.functions package, the convention is "Function" name is used for interfaces that can accept inputs of different types and return result of different type "Operator" are specialization of "Function" that take same type for all inputs and result Should we follow a similar naming convention for Complex functional interfaces? Thanks On Fri, 27 May 2022 at 21:34, Alex Herbert <alex.d.herb...@gmail.com> wrote: > On Thu, 26 May 2022 at 15:04, Gilles Sadowski <gillese...@gmail.com> > wrote: > > > > > > Next, I wanted to mention how I plan to start this project and was > hoping > > > to get some feedback. > > > > > > As per my proposal, the first thing I wanted to start with was the API > > > design which would have interfaces to represent complex numbers, > methods > > to > > > convert to/from linear primitive arrays, Java 8 functional interfaces > for > > > unary/binary operators and for functions for complex operations and > > > transforms involving: complex number and real numbers, complex vectors > > and > > > scalars, complex matrix, vectors and scalars. > > > > There are many items here. I would suggest breaking it down. Perhaps: > > 1. > interfaces to represent complex numbers > unary/binary operators and for functions for complex operations > > 2. > methods to convert to/from linear primitive arrays > > 3. > complex vectors and scalars, complex matrix, vectors and scalars. > > > Although not completely independant, we could discuss each in turn and see > what functionality is required. > > I will start with topic 1. Currently we have a single object, Complex, that > represents a complex number in cartesian form. It has a full set of > operations specified in ISO C99. I would suggest you have a look at the > specification as it has a lot of information about this [1]. > > There is a benchmark for these operations in the examples JMH module: > org.apache.commons.numbers.examples.jmh.complex.ComplexPerformance > > Ideally any changes to extract all the methods into a static class should > not impact performance. Many of the methods are quite involved and > therefore slow. However some methods such as those for > add/subtract/multiply/divide with real or imaginary scalars will be fast. > It would be interesting to see if abstraction to a static class impacts > their performance. These operations are not in the JMH benchmark so this > could be added to provide a reference point for these. > > > From your GH code you have the following interface: > > public interface ComplexFunction { > <R> R apply(double r, double i, ComplexResult<R> result); > } > > I cannot create a lambda function for this as the method has a generic type > parameter. This fails to compile. > > ComplexFunction f = (r, i, result) -> { > // conjugate > return result.apply(r, -i); > }; > > This can be solved by moving <R> to the interface declaration: > > public interface ComplexFunction<R> { > R apply(double r, double i, ComplexResult<R> result); > } > > But then all use of ComplexFunction has to be typed which can get noisy. It > is however explicit in what the function output will be (and we assume the > input is a complex number of some sort). > > > Q. Do we wish to support effectively duplication of operations by accepting > primitives and also a ComplexNumber type in the static methods: > > interface ComplexNumber { > double real(); > double imag(); > } > > class ComplexFunctions { > <R extends ComplexNumber> R sin(ComplexNumber c, ComplexResult<R> r) { > return sin(c.real(), c.imag(), r); > } > <R extends ComplexNumber> R sin(double r, double i, ComplexResult<R>) { > // ... > } > } > > There are various options for chaining methods together for sequential > operations on the same complex. Should this avoid repeat object allocation > by providing a MutableComplex holder: > > class MutableComplex implements ComplexNumber, > ComplexResult<MutableComplex> { > // allows read, write from/to real, imaginary parts > } > > > > Q. How to manipulate ComplexList: > > class ComplexList implements List<Complex> { > private double[] r; > private double[] i; > } > > You have forEach methods (note this is without the type parameter): > > void forEach(ComplexFunction fun); > > So how do I declare a function to pass to the list, that accepts the real > and imaginary parts and saves them back to the list. Currently you have the > ComplexFunction accept the real and imaginary parts. But what if it > accepted a ComplexNumber: > > public interface ComplexFunction<R> { > R apply(ComplexNumber c, ComplexResult<R> result); > } > > > The ComplexList need only provide a single object that acts as a read/write > cursor over the data by implementing both interfaces ComplexNumber and > ComplexResult: > > // Internal class > class ComplexCursor implements ComplexNumber, ComplexResult<Void> { > // Directly manipulated by the enclosing list > int index; > > @Override > public Void apply(double r, double i) { > ComplexList.this.r[index] = r; > ComplexList.this.i[index] = i; > return null; > } > > @Override > public double real() { > return ComplexList.this.r[index]; > } > > @Override > public double imag() { > return ComplexList.this.i[index]; > } > } > > I can write the method: > > void forEach(ComplexFunction<Void> fun) { > ComplexCursor cursor = new ComplexCursor(); > while (cursor.index < r.length) { > fun.apply(cursor, cursor); > cursor.index++; > } > } > > And call it like this: > > class ComplexFunctions { > static <R> R conj(ComplexNumber c, ComplexResult<R> r) { > return r.apply(c.real(), -c.imag()); > } > } > > > ComplexList l; > l.forEach(ComplexFunctions::conj); > > // Using a lambda > l.forEach((c, result) -> { > return result.apply(c.real(), -c.imag()); > }); > > > I've provided a few ideas there to get started. In summary here are some > requirements we should keep in mind: > > - Complex functions should be able to be declared with lambda functions > - The ComplexList allows read/write type operations on elements with > complex functions > > Alex > > > [1] http://www.open-std.org/JTC1/SC22/WG14/www/standards, specifically > WG14 > N1256 sections 7.3 (p 170) and Annex G (p 467). >