Gallaugher Gallaugher - 1 month ago 9
Swift Question

Array of closures in shorthand: Xcode Swift: Expression too complex to be solved in a reasonable time

I'm doing simple conversions (e.g. imperial to metric). I've created an array of tuples with (String, Closure) elements. This lets me put a string for the conversion (e.g. "miles to kilometers" into a picker control, and refer to the associated formula in a single line, passing the value to convert. The problem I'm having is when I use the $0 closure shorthand. I get an error "Expression too complex to solve in a reasonable time."

Here's the declaration & code that works:

Declared with ViewController properties below initial class statement:

var formulaTuple = [(convString: String, convFormula: ((Double) -> Double))]()


Assigned in viewDidLoad()

formulaTuple = [("miles to kilometers", {(a: Double) -> Double in return (a / 0.62137) }),
("kilometers to miles", {(a: Double) -> Double in return (a * 0.62137) }),
("feet to meters", {(a: Double) -> Double in return (a / 3.2808) }),
("yards to meters", {(a: Double) -> Double in return (a / 1.0936) }),
("meters to feet", {(a: Double) -> Double in return (a * 3.2808) }),
("meters to yards", {(a: Double) -> Double in return (a * 1.0936) }),
("inches to centimeters", {(a: Double) -> Double in return (a / 0.39370) }),
("centimeters to inches", {(a: Double) -> Double in return (a * 0.39370) }),
("fahrenheit to celsius", {(a: Double) -> Double in return ((a - 32) * (5/9)) }),
("celsius to fahrenheit", {(a: Double) -> Double in return (a * (9/5) + 32) }),
("quarts to liters", {(a: Double) -> Double in return (a / 1.05669) }),
("liters to quarts", {(a: Double) -> Double in return (a * 1.05669) }) ]


And working call in code where row is the row in the picker that was clicked in, inputValue is what's passed to be converted, and outputValue is the result of the conversion.

outputValue = formulaTuple[row].convFormula(inputValue)

Problem arises when I try to trim down the declaration using this syntax in viewDidLoad() instead of the synatax above:

formulaTuple = [("miles to kilometers", {$0 / 0.62137 }),
("kilometers to miles", {$0 * 0.62137 }),
("feet to meters", {$0 / 3.2808 }),
("yards to meters", {$0 / 1.0936 }),
("meters to feet", {$0 * 3.2808 }),
("meters to yards", {$0 * 1.0936 }),
("inches to centimeters", {$0 / 0.39370}),
("centimeters to inches", {$0 * 0.39370 }),
("fahrenheit to celsius", {($0 - 32) * (5/9) }),
("celsius to fahrenheit", {$0 * (9/5) + 32 }),
("quarts to liters", {$0 / 1.05669 }),
("liters to quarts", {$0 * 1.05669 }) ]


I thought this would be smoother, but it seems it breaks Xcode. Thoughts? Is my approach fundamentally unsound and a different one would be recommended?
Thanks!

Answer

Swift doesn't do well when you give it large array literals and then expect it to interpret the type. In your case, I would have expected this to work since your formulaTuple property already has an established type.

As a work around, you can first initialize a constant array and then assign it to your property:

let temp: [(String, (Double) -> Double)] = [
            ("miles to kilometers", {$0 / 0.62137 }),
            ("kilometers to miles", {$0 * 0.62137 }),
            ("feet to meters", {$0 / 3.2808 }),
            ("yards to meters", {$0 / 1.0936 }),
            ("meters to feet", {$0 * 3.2808 }),
            ("meters to yards", {$0 * 1.0936 }),
            ("inches to centimeters", {$0 / 0.39370}),
            ("centimeters to inches", {$0 * 0.39370 }),
            ("fahrenheit to celsius", {($0 - 32) * (5/9) }),
            ("celsius to fahrenheit", {$0 * (9/5) + 32 }),
            ("quarts to liters", {$0 / 1.05669 }),
            ("liters to quarts", {$0 * 1.05669 })
]

formulaTuple = temp

Alternative Answer

Tuples are really meant for temporary storage and to pass multiple results back from a function. You might want to consider using a struct as the type for the values of your array:

struct Conversion {
    let string: String
    let formula: (Double) -> Double
}

var conversions = [Conversion]()

conversions = [
    Conversion(string: "miles to kilometers", formula: {$0 / 0.62137 }),
    Conversion(string: "kilometers to miles", formula: {$0 * 0.62137 }),
    Conversion(string: "feet to meters", formula: {$0 / 3.2808 }),
    Conversion(string: "yards to meters", formula: {$0 / 1.0936 }),
    Conversion(string: "meters to feet", formula: {$0 * 3.2808 }),
    Conversion(string: "meters to yards", formula: {$0 * 1.0936 }),
    Conversion(string: "inches to centimeters", formula: {$0 / 0.39370}),
    Conversion(string: "centimeters to inches", formula: {$0 * 0.39370 }),
    Conversion(string: "fahrenheit to celsius", formula: {($0 - 32) * (5/9) }),
    Conversion(string: "celsius to fahrenheit", formula: {$0 * (9/5) + 32 }),
    Conversion(string: "quarts to liters", formula: {$0 / 1.05669 }),
    Conversion(string: "liters to quarts", formula: {$0 * 1.05669 })
]

outputValue = conversions[row].formula(inputValue)

Swift is much happier with this, and the temp workaround isn't necessary.