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
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With