Reputation: 2029
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
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
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
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
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