I've been developing a program which iterates Complex functions in order to generate Mandelbrot and Julia sets, coloured in various ways.
So as to be able to vary the function to be iterated, and to learn about lambdas, I tried to implemented the functions as an array of BinaryOperator<T>
- but javac complained that it didn't like arrays of generic types.
So I created my own non-generic interfaces to provide the same service, but just for my complex number class. It works. It allows me to change the function being iterated and compose functions.
I have read several ebooks about Java 8 lambdas and looked at a number of tutorials, but in none of these have I found any example of or reference to the idea of putting lambdas in an array. So I am wondering, is there a fundamental design flaw or type-safety flaw here that I am missing? Would it be better to use a List
instead of an array, so I can use the generic interfaces?
Here's the relevant code:
@FunctionalInterface
private interface ComplexFunction {
Complex apply(Complex z);
}
@FunctionalInterface
private interface BiComplexOperator {
Complex apply(Complex z, Complex w);
default BiComplexOperator andThen(ComplexFunction after) {
Objects.requireNonNull(after);
return (z, w) -> after.apply(apply(z, w));
}
default BiComplexOperator compose(ComplexFunction before) {
Objects.requireNonNull(before);
return (z, w) -> apply(before.apply(z), w);
}
}
private static final BiComplexOperator[] functionToIterate = new BiComplexOperator[] {
(z, c) -> Complex.sum(z.pow(powerOfZ), c), // 0
(z, c) -> Complex.sum(z.pow(powerOfZ).exp(), c), // 1
(z, c) -> Complex.sum(z.pow(powerOfZ).sqrt(), c), // 2
(z, c) -> Complex.sum(z.sqrt().pow(powerOfZ), c), // 3
(z, c) -> Complex.sum(z.pow(powerOfZ).exp().sqrt(), c), // 4
(z, c) -> Complex.sum(z.pow(powerOfZ).sqrt().exp(), c), // 5
(z, c) -> Complex.sum(z.pow(powerOfZ), new Complex(sin(c.x), cos(c.y))), // 6
(z, c) -> Complex.sum(z.pow(powerOfZ), Complex.difference(c, z)), // 7
null,
null
};
static {
functionToIterate[8] = functionToIterate[0].compose(Complex::recip).andThen(Complex::recip);
functionToIterate[9] = functionToIterate[0].andThen(Complex::sqrt).compose(z -> new Complex(z.y, -z.x));
}
And for reference, here's my Complex class:
class Complex {
public double x, y;
public static final Complex ZERO = new Complex(0.0, 0.0);
public static final Complex ONE = new Complex(1.0, 0.0);
public static final Complex I = new Complex(0.0, 1.0);
public static final Complex MIN_VALUE = new Complex(Double.MIN_VALUE, 0.0);
public Complex() {
this.x = 0.0;
this.y = 0.0;
}
public Complex(double x, double y) {
this.x = x;
this.y = y;
}
public Complex(Complex z) {
this.x = z.x;
this.y = z.y;
}
public boolean equals(Complex z) {
return z == null ? false : this.x == z.x && this.y == z.y;
}
public boolean equals(double x) {
return this.x == x && this.y == 0.0;
}
public static Complex sum(Complex z, Complex w) {
return new Complex(z.x + w.x, z.y + w.y);
}
// overloaded for convenience to take 3 arguments...
public static Complex sum(Complex z, Complex w, Complex v) {
return sum(sum(z, w), v);
}
public static Complex sum(Complex z, double s) {
return new Complex(z.x + s, z.y);
}
public static Complex sum(Complex z, Complex w, double s) {
return sum(sum(z, w), s);
}
public static Complex difference(Complex z, Complex w) {
return new Complex(z.x - w.x, z.y - w.y);
}
public static Complex product(Complex z, Complex w) {
return new Complex(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
}
// overloaded for convenience to take 3 arguments...
public static Complex product(Complex z, Complex w, Complex v) {
return product(product(z, w), v);
}
public static Complex product(Complex z, double s) {
return new Complex(z.x * s, z.y * s);
}
public static Complex product(Complex z, Complex w, double s) {
return product(product(z, w), s);
}
public static Complex quotient(Complex z, Complex w) {
double denom = w.x * w.x + w.y * w.y;
if (denom == 0.0) {
//String errorMsg = "2nd argument to Complex.quotient() must not be zero.";
//throw new IllegalArgumentException(errorMsg);
denom = Double.MIN_VALUE;
}
return new Complex((z.x * w.x + z.y * w.y) / denom, (z.y * w.x - z.x * w.y) / denom);
}
public Complex recip() {
return Complex.quotient(ONE, this);
}
public Complex squared() {
return new Complex(this.x * this.x - this.y * this.y, 2.0 * this.x * this.y);
//return new Complex(this.x * this.x - this.y * this.y, 1.4142135623730950488016887242097 * this.x * this.y);
}
public Complex neg() {
return new Complex(-this.x, -this.y);
}
public Complex bar() {
return new Complex(this.x, -this.y);
}
public double abs() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
public Complex pow(int n) {
if (n < 0 || n > 8192) {
String errorMsg = "Argument to Complex.pow(int n) must be positive and <= 8192";
throw new IllegalArgumentException(errorMsg);
}
switch(n) {
case 0:
return ONE;
case 1:
return this;
case 2:
return this.squared();
case 4:
case 8:
case 16:
case 32:
case 64:
case 128:
case 256:
case 512:
case 1024:
case 2038:
case 4096:
case 8192:
return this.pow(n / 2).squared();
default:
// in this linear recursion, when n gets down to the
// first power of 2 less than it, we jump into the exponential
// recursive cycle...
return product(this.pow(n-1), this);
}
}
public Complex exp() {
return product(new Complex(cos(this.y), sin(this.y)), Math.exp(this.x));
}
public Complex sqrt() {
double rootR = Math.sqrt(this.abs());
double thetaOver2 = atan2(this.y, this.x) / 2.0;
return new Complex(rootR * cos(thetaOver2), rootR * sin(thetaOver2));
}
public String toString() {
return "" + this.x + " + " + this.y + "i";
}
} // end class Complex