breaktop
breaktop

Reputation: 2029

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

Upvotes: 9

Views: 4300

Answers (4)

michaelI
michaelI

Reputation: 11

 private let mathOperationTable : Dictionary<String,Operation> =
 [    "π" : Operation.Constant(Double.pi),
      "e" : Operation.Constant(M_E),
      "√" : Operation.UnaryOperation(sqrt),
      "±" : Operation.UnaryOperation({-$0}),
      "+" : Operation.BinaryOperation(+),
      "−" : Operation.BinaryOperation(-),
      "×" : Operation.BinaryOperation(*),
      "÷" : Operation.BinaryOperation(/),
      "=" : Operation.Equals
 ]

That's is my verion. I can get rid of the errors in the editor by just rebuilding/running which seems to force a fresh and more accurate compile than that available in the editor. And if I remove all the "Operation" -

 private let mathOperationTable : Dictionary<String,Operation> =
 [    "π" : .Constant(Double.pi),
      "e" : .Constant(M_E),
      "√" : .UnaryOperation(sqrt),
      "±" : .UnaryOperation({-$0}),
      "+" : .BinaryOperation(+),
      "−" : .BinaryOperation(-),
      "×" : .BinaryOperation(*),
      "÷" : .BinaryOperation(/),
      "=" : .Equals
 ]

...it seems to be too much type inferencing, and I can't compile my way out of the error. Funny thing, if I remove just a couple of the enum "Operation" type clarifications(& not all), it still works. So yes, a bug.

Upvotes: 0

Nabha Cosley
Nabha Cosley

Reputation: 1118

While a little more verbose, this worked while saving me from having to add any additional code outside of the operations dictionary:

// operations list
private let operations : Dictionary<String, Operation> = [
    "π" : .Constant(M_PI),
    "e" : .Constant(M_E),
    "√" : .UnaryOperation(sqrt),
    "cos" : .UnaryOperation(cos),
    "×" : .BinaryOperation({ (op1, op2) -> Double in return op1 * op2 }),
    "÷" : .BinaryOperation({ (op1, op2) -> Double in return op1 / op2 }),
    "+" : .BinaryOperation({ (op1, op2) -> Double in return op1 + op2 }),
    "−" : .BinaryOperation({ (op1, op2) -> Double in return op1 - op2 }),
    "=" : .Equals
]

It's just stepping the super-awesome-Swift-inference-thing back a few steps so the compiler doesn't have to do as much work.

Upvotes: 0

Bill Mitchell
Bill Mitchell

Reputation: 111

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.

Upvotes: 1

Aaron Rasmussen
Aaron Rasmussen

Reputation: 13316

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.

Upvotes: 6

Related Questions