Swift compilation time with nil coalescing operator - swift

After reading of the article about swift compiling time. I am interested in why usage of more than 2 sequence coalescing operator increase compilation time significantly.
Example:
Compilation time 3.65 sec.
func fn() -> Int {
let a: Int? = nil
let b: Int? = nil
let c: Int? = nil
return 999 + (a ?? 0) + (b ?? 0) + (c ?? 0)
}
Compilation time 0.09 sec.
func fn() -> Int {
let a: Int? = nil
let b: Int? = nil
let c: Int? = nil
var res: Int = 999
if let a = a {
res += a
}
if let b = b {
res += b
}
if let c = c {
res += c
}
return res
}

I'm almost certain that this has to do with type inference. When interpreting all of those + and ?? operators the compiler is doing a lot of work behind the scenes to infer the types of those arguments. There are around thirty overloads for the + operator alone and when you chain several of them together you are making the compiler's job much more complicated than you might think.

Related

Terminated by signal 4

Already checked other question on this topic, and I can't seem to utilize it to resolve my issue, so I'm creating a new question in order to see if I can't gain some insight on this issue and help others who may run into this using the Online Swift Playground.
Getting Terminated by signal 4 using the following code:
import Foundation
//enter equation here
var equation: String = "2 +( 3* 4)/ 2+22 ="
var equationWithoutWhitespace = equation.filter {!$0.isWhitespace}
//converts String equationWithoutWhitespace to array of Charcters e
let e = Array(equationWithoutWhitespace)
func add(_ firstVal: Int, _ secondVal: Int) -> Int {return Int(firstVal + secondVal)}
func sub(_ firstVal: Int, _ secondVal: Int) -> Int {return Int(firstVal - secondVal)}
func mul(_ firstVal: Int, _ secondVal: Int) -> Int {return Int(firstVal * secondVal)}
func div(_ firstVal: Int, _ secondVal: Int) -> Int {return Int(firstVal / secondVal)}
func power(_ firstVal: Double, _ secondVal: Double) -> Int {return Int(pow(firstVal,secondVal))}
func root(_ firstVal: Double, _ secondVal: Double) -> Int {return Int(pow(firstVal,1/secondVal))}
func checkParenthesis(_ equation: [Character]) -> (low: Int, high: Int){
var low = 0
var high = 0
for (index,value) in equation.enumerated() {
if(equation[index] == "("){
low = index
}
else if(equation[index] == ")"){
high = index
}
}
return (low, high)
}
func doMath(firstVal: Character, op: Character, secondVal: Character) -> Int {
var firstVar: Int! = Int("\(firstVal)")
var secondVar: Int! = Int("\(secondVal)")
switch op {
case "+":
return add(firstVar,secondVar)
case "-":
return sub(firstVar,secondVar)
case "*":
return mul(firstVar,secondVar)
case "/":
return div(firstVar,secondVar)
case "x",
"X":
var firstV: Double! = Double("\(firstVar)")
var secondV: Double! = Double("\(secondVar)")
return power(firstV,secondV)
case "r",
"R":
var firstV: Double! = Double("\(firstVar)")
var secondV: Double! = Double("\(secondVar)")
return root(firstV,secondV)
default:
print("error with operation detection")
return 0
}
}
//create new equation
var e2 = e
//get index of parenthesis & operation
var low = checkParenthesis(e2).low
var high = checkParenthesis(e2).high - 1
var op = low + 1
//if there were parenthesis, do this
if(low != 0 && high != 0){
//remove parenthesis
e2.remove(at: checkParenthesis(e2).low)
e2.remove(at: checkParenthesis(e2).high)
print(doMath(firstVal: e2[low],op: e2[op],secondVal: e2[high]))
}
Edit: this is a partial snippet of the complete project, it is essentially a text based calculator but it utilizes different rules than normal PEMDAS
I was looking over it again today, and I found using print(e2[low], e2[op], e2[high], low, op, high), I was able to check what values I was getting for my command, and realized I was 1 position off, because earlier I was compensating for removing the parenthesis, but later moved that function further down in the program. Because of this, var high = checkParenthesis(e2).high - 1 was 1 value off and not giving me an integer, but instead a character. Changing the - 1 to - 2 fixed the issue and everything works as it should. Thanks to everyone for your help!

Why do `map(foo)` and `map{ foo($0) }` return different results?

For the following code snippet, why do res1 and res2 have different values?
func test() {
let a: String? = nil
let b: String? = nil
let foo: (String?) -> Int = { $0 == nil ? 0 : 1}
let res1 = [a, b].compactMap { $0 }.map(foo)
let res2 = [a, b].compactMap { $0 }.map { foo($0) }
print("res1: ", res1)
print("res2: ", res2)
}
The output is
res1: [0, 0]
res2: []
This appears to be a result in how the compiler selects the types of [a, b].compactMap based on the downstream operation. You can see this by inspecting the types of the arguments as they pass through the functions:
func printType<T>(_ i: String, _ v: T) -> T {
print(i, "->", T.self)
return v
}
func test() {
let a: String? = nil
let b: String? = nil
let foo: (String?) -> Int = { $0 == nil ? 0 : 1 }
let res1 = printType("cm1", printType("a1", [a, b]).compactMap { $0 }).map(foo)
let res2 = printType("cm2", printType("a2", [a, b]).compactMap { $0 }).map { foo($0) }
print("res1: ", res1)
print("res2: ", res2)
}
test()
// a1 -> Array<Optional<Optional<String>>>
// cm1 -> Array<Optional<String>>
// a2 -> Array<Optional<String>>
// cm2 -> Array<String>
// res1: [0, 0]
// res2: []
It appears that:
In the case of res1:
Because foo takes a String, map(foo) is typed such that a String? is passed through — and for map to be receiving a String?, compactMap must be returning a [String?]
In order for compactMap to be returning a [String?], its input must be a [String??]
Although [a, b] defaults to being a [String?], the compiler can also implicitly upcast it to a [String??], so that's what it does
Hence, only one layer of optionality is removed from [String??], and each of the String? values is passed to the map and into foo
In the case of res2, the compiler isn't restricted as heavily by map { foo($0) }, since $0 can be implicitly upcast inside of the closure before being passed to foo, and this is what the compiler prefers:
$0 is a String which is upcast to String? before being passed to foo
For map to receive String values, compactMap must return [String]
Since compactMap is returning a [String], its input must be [String?], which [a, b] is, no upcasting needed
Hence, now compactMap is actually filtering the nils out of [String?] into [String], and since no values are left, foo is never even called (you can see this with more print statements inside of foo
This sort of thing is very situational in the compiler, and you happened to have found a specific case where this happens. For instance, the compiler parses the results of single-statement closures differently from multi-statement closures: if you turn compactMap { $0 } into compactMap { _ = 1 + 1; return $0 }, the closure will parsed differently, and type checking will occur in a different order, resulting in [] in both cases:
let res1 = printType("cm1", printType("a1", [a, b]).compactMap { _ = 1 + 1; return $0 }).map(foo)
let res2 = printType("cm2", printType("a2", [a, b]).compactMap { _ = 1 + 1; return $0 }).map { foo($0) }
// a1 -> Array<Optional<String>>
// cm1 -> Array<Optional<String>>
// a2 -> Array<Optional<String>>
// cm2 -> Array<Optional<String>>
// res1: [0, 0]
// res2: [0, 0]
In this case, the compiler actually ended up preferring the surprising case in both instances, since return $0 allows the compiler to upcast from String? ➡ String?? (and then filter back down from [String??] ➡ [String?])!
Either way, this seems worthy of a report on https://bugs.swift.org, since the behavior is incredibly surprising. I will be happy to add test cases and comments to the report if you go ahead and file.

How to add two Optionals?

I'm coming from a C++ background, but I'm learning Swift 4 for MetalKit. Since I'm only used to C++, the whole focus on "optionals" comes a little foreign to me. While reading along with the Swift 4 book published by Apple, I came along the following problem:
Say I have two "String"s a and b:
let a = "12"
let b = "24"
I want to add these two together. It is bad practise AND illegal to write
let c = Int(a) + Int(b)
Because a and b are both Strings, their typecast into Int is an optional: the conversion may have failed. The solution seems to be
if let c = Int(a), let d = Int(b)
{
let e = c + d
}
But this is a bit of a hassle: I'm copying way more than I would in a C program, where I could simply add a and b and then test whether the result has a value. Is there a more efficient, better way to perform this addition?
As #rmaddy said in his comment, this is the whole point of optionals. It is supposed to make you work at it, resulting in safer code.
If you are willing to go to a bit of up-front work, you can create a custom operator that will throw an error if the result is converting to an Int is nil:
enum Err: Error {
case nilValue
}
func +(lhs: String, rhs: String)throws -> Int {
guard let c = Int(lhs), let d = Int(rhs) else {
throw Err.nilValue
}
return c + d
}
(You might have to add infix operator + to this snippet.)
You can then use it like this:
do {
let i: Int = try a + b
print(i)
} catch {
// Catch error here
}
Or use try?. This will return nil if an error is thrown:
let i: Int? = try? a + b
If you don't want to use the type annotations, you can give the operator a different name, i.e.:
infix operator +?: AdditionPrecedence
func +?(lhs: String, rhs: String)throws -> Int
If you're looking for a way to write this in one line, you can take advantage of the map and flatMap variants for optionals:
let a = "12"
let b = "24"
let c = Int(a).flatMap { aʹ in Int(b).map { bʹ in aʹ + bʹ }}
c is of type Optional<Int> and will be nil when either Int(a) or Int(b) fails. The outer map operation must be a flatMap to get the correct result type. If you replace flatMap with map, the type of c would be Optional<Optional<Int>>, or Int??.
Whether you consider this readable is at least partly a matter of familiarity with the concept of mapping over optionals. In my experience, most Swift developers prefer unwrapping with if let, even if that results in more than one line.
Another alternative: wrap this pattern of unwrapping two optionals and applying a function to the unwrapped values in a generic function:
func unwrapAndApply<A, B, Result>(_ a: A?, _ b: B?, _ f: (A, B) -> Result) -> Result? {
guard let a = a, let b = b else { return nil }
return f(a, b)
}
This function works on all inputs, regardless of the underlying types. Now you can write this to perform the addition:
let d = unwrapAndApply(Int(a), Int(b), +)
The unwrapAndApply function only works on two input arguments. If you need the same functionality for three, four, five, … inputs, you’ll have to write additional overloads of unwrapAndApply that take the corresponding number of arguments.
You have many ways in which you can do this. I will show you one that it is appropriate if you have to do this often:
extension Int {
static func addStrings(_ firstString: String, _ secondString: String) -> Int? {
guard let firstNumber = Int(firstString) else { return nil }
guard let secondNumber = Int(secondString) else { return nil }
return firstNumber + secondNumber
}
}
you should use it like this:
let number = Int.addStrings("1", "2")
I am using one awesome feature of Swift - extensions. With them you can add methods and computed variables to every class you want. Optionals and extensions are very important things when it comes to Swift development. You should read Apple docs carefully.
You can define your own operator to add two optionals together:
let a = "12"
let b = "24"
infix operator ?+: AdditionPrecedence
func ?+ (left: Int?, right: Int?) -> Int? {
guard let left = left,
let right = right else {
return nil
}
return left + right
}
let c = Int(a) ?+ Int(b)
The result is an optional. If you don't want the result to be optional you need to provide a suitable default value. For instance if you think 0 is appropriate:
infix operator ?+: AdditionPrecedence
func ?+ (left: Int?, right: Int?) -> Int {
guard let left = left,
let right = right else {
return 0
}
return left + right
}
There are a couple of ways to handle optionals besides the if let method you showed.
One is to place a guard let statement at the beginning of the block of code in which you are using the optionals. This allows you to avoid having tons of nested if let statements:
guard let c = Int(a), let d = Int(b) else { return }
// use `c` and `d` as you please
// ...
You can also use the nil coalescing operator to define a default value (e.g. 0):
let c = (Int(a) ?? 0) + (Int(b) ?? 0)
In this situation, if either Int(a) or Int(b) fails, they will be replaced with 0, respectively. Now c is an Int instead of an Int? and can be used freely without unwrapping. This may or may not be appropriate depending on what can happen if you use a default value rather than the intended one.
Further reading: What is an optional value in Swift?
Alternatively, you can create a custom operator to allow you to numerically "add" two strings:
infix operator +++: AdditionPrecedence
func +++(_ a: String, _ b: String) -> Int? {
if let intA = Int(a), let intB = Int(b) {
return intA + intB
} else {
return nil
}
}
// use it like so:
let c = "12" +++ "24"
// now c is Int? and you can check if the result is optional
if let d = c {
}
More about custom operators in the Swift Documentation: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html

Nicer syntax for ternary with a let?

Is there a nicer way to do the assignment to DEF in the following example? I want to convert type A to Type B, but still preserve the nil possibility whenever I can.
Can't seem to stumble into a better way of doing this, however. Suggestions?
class ABC {
var DEF: Int?
func X (someValue: Int8?) {
DEF = someValue != nil ? Int(someValue) : nil
}
}
Swift 1:
class ABC {
var DEF: Int?
func X (someValue: Int8?) {
DEF = someValue.map{Int($0)}
}
}
Swift 2:
class ABC {
var DEF: Int?
func X (someValue: Int8?) {
DEF = someValue.map(Int.init)
}
}
map() takes an optional, unwraps it, and applies a function to it. If the optional resolves to nil, map() returns nil.
You are describing optional map:
var i: Int? = 2
let j = i.map { $0 * 2 } // j = .Some(4)
i = nil
let k = i.map { $0 * 2 } // k = nil
Think of this map like array or other collection map, where optionals are collections that have either zero (nil) or one (non-nil) element.
Note, if the operation you want to perform itself returns an optional, you need flatMap to avoid getting a double-optional:
let s: String? = "2"
let i = s.map { Int($0) } // i will be an Int??
let j = s.flatMap { Int($0) } // flattens to Int?

Is there an equivalent to optional chaining with arithmetic operators?

I have two optional numbers that I add. Right now it looks like this:
if let a = optionalA {
if let b = optionalB {
return a + b
}
}
return nil
For methods, there's the more convenient optional chaining syntax, like optionalA.?method syntax. Is there an equivalent for arithmetic operators that would return nil if either side was nil?
It's possible to create an operator +? that does just this like this:
infix func +?(a: Int?, b: Int?) -> Int? {
if aa = a {
if bb = b {
return aa + bb
}
}
return nil
}
but I'm wondering if there's another, built-in way to do that.
I dont think there is built in way. But you can make your own operator function:
func +(lhs: Int?, rhs: Int?) -> Int? {
if let a = lhs {
if let b = rhs {
return a + b
}
}
return nil
}
var a: Int? = 1
var b: Int? = 2
var c = a + b
Here's the custom operator approach updated for Swift 3:
infix operator +?
func +?(a: Int?, b: Int?) -> Int? {
if let aa = a, let bb = b {
return aa + bb
}
return nil
}