breaktop breaktop - 5 months ago 29
Swift Question

Swift compilation error - Expression was too complex to be solved in reasonable time

I'm making a very simple calculator and I'm getting a really strange compile time error. I'm getting the following error in my

CalculatorBrain
class:


Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions


Here is the code that generated the error

private var operations: Dictionary<String, Operation> = [
"π" : .Constant(M_PI),
"±" : .UnaryOperation({ -$0 }),
"×" : .BinaryOperation({ $0 * $1 }),
"÷" : .BinaryOperation({ $0 / $1 }),
"+" : .BinaryOperation({ $0 + $1 }),
"−" : .BinaryOperation({ $0 - $1 }),
"=" : .Equals
]


The strange thing is that if I remove the following:

"±" : .UnaryOperation({ -$0 })
"+" : .BinaryOperation({ $0 + $1 })
"−" : .BinaryOperation({ $0 - $1 })


The code compiles, otherwise it throws the error.

Another strange thing is that if I change those to:

"±" : .UnaryOperation({ (op1: Double) -> Double in return -op1 })
"+" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 + op2 })
"−" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 - op2 })


The code compiles and does not throw the error.

I'm kind of confused as to why it works when using the operators the
*
and
/
and not
-
and
+


Just in case you're wondering how
Operation
is implemented, here it is:

private enum Operation {
case Constant(Double)
case UnaryOperation((Double) -> Double)
case BinaryOperation((Double, Double) -> Double)
case Equals
}


I'm using Swift version 2.2 on Xcode Version 7.3.1

Answer

It has to do with type inference. See this answer for a more general discussion.

In your specific case, it is the type inference going on in the closures that is causing the compiler to have problems. I believe that is why when you provide specific type annotations in your closure expressions, the compiler is able to resolve things.

I would recommend storing your closures in external constants:

let addition: (Double, Double) -> Double = { $0 + $1 }
let subtraction: (Double, Double) -> Double = { $0 - $1 }
// etc...

Then use those constants in your operations dictionary:

private var operations: Dictionary<String, Operation> = [
    "+" : .BinaryOperation(addition),
    "−" : .BinaryOperation(subtraction)
    /// etc...
]

That will give the compiler what it needs to resolve everything, and it is also a bit clearer (I think).

EDIT: I realized after I posted this that there is an even more concise way to write the closures:

let addition: (Double, Double) -> Double = (+)
let subtraction: (Double, Double) -> Double = (-)

That's even clearer (I think).

Some other options that will satisfy the compiler and reduce some of the duplication of code include creating an array of binary operations:

let binaryOps: [((Double, Double) -> Double)] = [(+), (-), (/), (*)]

Then access them by index:

private var operations: Dictionary<String, Operation> = [
    "+" : .BinaryOperation(binaryOps[0]),
    "−" : .BinaryOperation(binaryOps[1])
    /// etc...
]

Or creating a typealias:

typealias BinaryOp = (Double, Double) -> Double

let addition: BinaryOp = (+)
let subtraction: BinaryOp = (-)

These reduce some of the verbosity, but however you do it, I think you are going to have to use specific type annotations somewhere to satisfy the compiler.

Comments