2

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
John
  • 368
  • 1
  • 12
  • 2
    lambda expressions are instances of objects implementing a given interface, and they can be stored in collections just like any other objects. But arrays don't play nice with generics in general (lambdas or not). You should favor collections instead (Lists, Sets, etc.). – JB Nizet Mar 08 '16 at 21:54
  • Possible duplicate of [How to create a generic array in Java?](http://stackoverflow.com/questions/529085/how-to-create-a-generic-array-in-java) – Yosef Weiner Mar 08 '16 at 21:54
  • Show the code where you need to access these functions by index, or iterate over them. That's the part that isn't making sense. – erickson Mar 08 '16 at 22:02
  • OK, I'm getting the idea that (as @JB Nizet commented) "arrays don't play nice with generics in general". And the code using reflection in the quoted question [link](http://stackoverflow.com/questions/529085/how-to-create-a-generic-array-in-java) looks ugly! But am I ok having my non-generic array of lambdas, or is there some reason I should favour a 'List' instead? – John Mar 08 '16 at 22:15
  • @erickson here's the function: ' private Complex f(double x, double y, Complex c, BiComplexOperator function) { return f(new Complex(x, y), c, function); } private Complex f(Complex z, Complex c, BiComplexOperator function) { return function.apply(z, c); }' and here's the call: ' while (p * p + q * q < escapeRadius2 && numIterations < maxIterations) { numIterations++; z = f(p, q, c, functionToIterate[functionIndex]); // apply the function p = z.x; q = z.y; }' – John Mar 08 '16 at 22:26
  • Is `functionIndex` incremented in some bigger loop? Or is it fixed (for example, selected by user input)? – erickson Mar 08 '16 at 22:43

1 Answers1

2

You should use a List, not an array.

The downside to using an array is that, to allow the compiler to assure the same level of type safety provided by a List, you have to duplicate the core functional interfaces with your own, non-generic versions.

There is no downside to using a List. With runtime optimization, using an ArrayList will give you the same performance as an array, and even a linked list implementation will perform well using an iterator. Given the drawbacks, albeit small, of an array, why wouldn't you use a List instead?

erickson
  • 249,448
  • 50
  • 371
  • 469
  • 3
    Or use an `enum` and get naming+iteration! :O – Austin D Mar 08 '16 at 22:00
  • @erickson Thanks, this was the answer I needed. I will re.code using a 'List'. I'm wondering how best to initialise it though: I could start with an 'Array' and use 'Arrays. toList()', or use a loop and 'List.add()' for each function. Both of these seem a bit ugly, though. Is there a better way, equivalent to the syntax of using {element 0, element 1...} for arrays? – John Mar 10 '16 at 15:27
  • @John From your original code, it looks like each function is quite different; there's not a way to generate them iteratively, right? If that's the case, I'd just use a series of `list.add((z, c) -> ...);` statements. It's clear, readable, and generally formats better than long array initializers do. – erickson Mar 10 '16 at 16:27