Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 951
breaktop Avatar asked May 18 '16 17:05

breaktop


2 Answers

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.

like image 187
Aaron Rasmussen Avatar answered Oct 16 '22 13:10

Aaron Rasmussen


Here is a workaround which seems less verbose:

typedef d2d = (Double,Double)->Double
enum Operation {
    case Constant(Double)
    case Binary(d2d)
    case Unary((Double)->Double)
    case Equals
}

let b:[String:Operation] = [
    "+": .Binary({$0+$1} as d2d),
    "*":  .Binary({$0*$1} as d2d),
    "/":  .Binary({$0/$1} as d2d),
    "-":  .Binary({$0-$1} as d2d),
    "mod": .Binary( {$0%$1} as d2d),
]

func performOperation(symbol:String, op1:Double, op2:Double)->Double?{
    if let operation = b[symbol] {
        switch operation {
        case .Binary(let op ) :
            return(op(op1,op2))
        default:
            break
        }
    }
    return(nil)
}



performOperation("*", op1:3, op2:7)  // 21

I came to this problem, incidentally (as, I expect breaktop did), from Paul Hegarty's online course from Stanford on IOS development using Swift: https://itunesu.itunes.apple.com/WebObjects/LZDirectory.woa/ra/directory/courses/1104579961/feed.

Presumably this is a bug in the Swift compiler.

like image 39
Bill Mitchell Avatar answered Oct 16 '22 12:10

Bill Mitchell