9

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

breaktop
  • 1,899
  • 4
  • 37
  • 58
  • 1
    How did you implement `Operation`? – Ozgur Vatansever May 18 '16 at 17:04
  • I've defined ```Operation```. It's an enum with associated values e.g. ```case BinaryOperation((Double, Double) -> Double)```. The thing is its fine when using the the operators ```*``` and ```/``` but I get compilation errors with operators ```-``` and ```+``` – breaktop May 18 '16 at 17:09
  • I'd actually asked you to post your code showing how you implemented `Operation` enum. – Ozgur Vatansever May 18 '16 at 17:12
  • @ozgur Ahh sorry. Just updated post with the ```Operation``` enum – breaktop May 18 '16 at 17:16
  • 1
    Have you reviewed [these search results](http://stackoverflow.com/search?q=%5Bswift%5D+Expression+was+too+complex+to+be+solved+in+reasonable+time)? – rmaddy May 18 '16 at 17:18

4 Answers4

6

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.

Community
  • 1
  • 1
Aaron Rasmussen
  • 13,082
  • 3
  • 42
  • 43
  • I was trying to avoid duplication of code which is why I went with my approach where I can reuse/add additional operations in ```Operation```. With your solution, it looks like you will end up with code duplication. Addition and Subtraction are binary operation so it would be neater (I think) to have one function type that covers that operation rather than two. – breaktop May 18 '16 at 17:31
  • I understand your reasoning. But one way or another you'll have to help the compiler with the types or you'll keep getting a similar error. You'll have to pick your poison :) – Aaron Rasmussen May 18 '16 at 17:44
1

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.

  • Thanks, the "as" notation helps with a relatively succinct, localized workaround. Doing the same course, hoping it will work eventually, I'm adding just to the ones that need it, .BinaryOperation({ $0 + $1 } as (Double,Double) -> Double) – dbreaux Dec 16 '16 at 22:54
0

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.

Nabha Cosley
  • 1,118
  • 10
  • 18
0
 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.

michaelI
  • 11
  • 3