How to compare types in Swift? - swift

I declare two variables in a Swift file, with the following values:
let m = 1
let n = 2
Now, I would like to compare their types:
type(of: m) === type(of: n)
The following compliling error occurs:
Argument type 'String.Type' expected to be an instance of a class or class-constrained type
Is there a way to compare types ?
Also, why does this line fail ?
String === String

The === operator is only defined for reference types, since it checks reference (pointer) equality, so you can't use it on metatypes of value types.
However, you can use the normal equality operator, ==.
type(of: m) == type(of: n)
As for String === String, that's not even valid code. What you need if String.self == String.self. You can access metatypes using TypeName.self.

Related

Compare two objects in Swift without casting to a specific type

I'm trying to compare two objects whose type is known at run time but not at compile time. So far I have the following function which seems to work as written:
/// Compares two objects of the given type.
/// Returns -1 if a is "less than" b, 1 if a is "greater than" b, 0 if they are equal, and nil if no comparison could be made.
func compare<T: Comparable>(type: T.Type, a: Any?, b: Any?) -> Int? {
guard let at = a as? T, let bt = b as? T else { return nil }
return at < bt ? -1 : at > bt ? 1 : 0
}
The problem is, the type is not necessarily known to comply with the Comparable protocol at compile time; I really need to be able to pass in any type (Any.Type), not just Comparable ones. I'd then like the function to return nil if the passed-in type does not conform to Comparable. How can I do this?
Edit (30/08/2018): Some more context. I'm using this function to sort various arrays of strings, integers, and other Comparable types. However, because these arrays are retrieved via reflection the element types are not known at compile time. I know that they will always be Comparable but the compiler doesn't. To resolve this, I'm passing in the type as a separate parameter as shown. However, because I'd like to keep the logic as general as possible I'm performing this sort function inside a conditional statement which chooses the necessary type from an array. The type of this array must be [Any.Type] to hold the required types, even though all its contents conform to Comparable (String.Type, Date.Type, etc.).

Cannot subscript a value of type 'inout Array<Array<Any>>

I have a 2D array : var matrixOfMutableObjects: Array<Array<Any>>
But when i try to check if the value on a certain position of the Array is a enum that i created the compiler complains: Cannot subscript a value of type 'inout Array<Array<Any>>
if(x < 0 || x > ROWS || y < 0 || y > COLS){
return false;
} else if (matrixOfMutableObjects[x][y] != enumObject.NOTHING){
return false;
}
Why cant i verify if the element in the position x,y of my Array is of the type enumObject.NOTHING that i created??
public enum enumObject {
case NOTHING
case Wall
}
Pattern matching (used for matching cases in enum instances) and equality testing are two different things.
Also, an object wrapped in Any is only known to the compiler as Any, so you need to perform an attempted conversion to a given type prior to applying e.g. pattern matching vs cases of that type (say, if the type converted to is an enum).
E.g.:
public enum enumObject {
case NOTHING
case Wall
}
var matrixOfMutableObjects: Array<Array<Any>> = [
[1, "two"],
[enumObject.NOTHING, 4.2]
]
let x = 1
let y = 0
// no bounds checking in this simple example!
if case .some(.NOTHING) = matrixOfMutableObjects[x][y] as? enumObject {
print("Is nothing!")
}
Or, using the ? syntactic sugar:
// ...
if case .NOTHING? = matrixOfMutableObjects[x][y] as? enumObject {
print("Is nothing!")
}
Note that the outer .some(...) pattern matching (or ? sugar) checks that the attempted conversion of the element to enumObject is successful. If that is the case, an additional pattern matching of is performed for the wrapped object (which is then known to be of type enumObject) to a given case of enumObject (.NOTHING, specifically).
Curiosities of Swift: avoiding the explicit type conversion
As pointed out by #Hamish in a comment below, it seems that enum case pattern matching can actually perform the conditional type casting for you, meaning you needn't resort to the explicit casting and nested pattern matching above (as? and .some(...)/?, respectively), but can simply use pattern matching against the NOTHING case directly, given that you also supply the enum type:
if case enumObject.NOTHING = matrixOfMutableObjects[x][y] {
print("Is nothing!")
}
Finally, note that the Swift API Guidelines prescribes lowercase enum cases, so you might want to use the cases nothing and wall, rather than NOTHING and Wall, and additionally CamelCase name convention for types (so prefer EnumObject over enumObject).

How flatMap API contract transforms Optional input to Non Optional result?

This is the contract of flatMap in Swift 3.0.2
public struct Array<Element> : RandomAccessCollection, MutableCollection {
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
}
If I take an Array of [String?] flatMap returns [String]
let albums = ["Fearless", nil, "Speak Now", nil, "Red"]
let result = albums.flatMap { $0 }
type(of: result)
// Array<String>.Type
Here ElementOfResult becomes String, why not String? ? How is the generic type system able to strip out the Optional part from the expression?
As you're using the identity transform { $0 }, the compiler will infer that ElementOfResult? (the result of the transform) is equivalent to Element (the argument of the transform). In this case, Element is String?, therefore ElementOfResult? == String?. There's no need for optional promotion here, so ElementOfResult can be inferred to be String.
Therefore flatMap(_:) in this case returns a [String].
Internally, this conversion from the closure's return of ElementOfResult? to ElementOfResult is simply done by conditionally unwrapping the optional, and if successful, the unwrapped value is appended to the result. You can see the exact implementation here.
As an addendum, note that as Martin points out, closure bodies only participate in type inference when they're single-statement closures (see this related bug report). The reasoning for this was given by Jordan Rose in this mailing list discussion:
Swift's type inference is currently statement-oriented, so there's no easy way to do [multiple-statement closure] inference. This is at least partly a compilation-time concern: Swift's type system allows many more possible conversions than, say, Haskell or OCaml, so solving the types for an entire multi-statement function is not a trivial problem, possibly not a tractable problem.
This means that for closures with multiple statements that are passed to methods such as map(_:) or flatMap(_:) (where the result type is a generic placeholder), you'll have to explicitly annotate the return type of the closure, or the method return itself.
For example, this doesn't compile:
// error: Unable to infer complex closure return type; add explicit type to disambiguate.
let result = albums.flatMap {
print($0 as Any)
return $0
}
But these do:
// explicitly annotate [ElementOfResult] to be [String] – thus ElementOfResult == String.
let result: [String] = albums.flatMap {
print($0 as Any)
return $0
}
// explicitly annotate ElementOfResult? to be String? – thus ElementOfResult == String.
let result = albums.flatMap { element -> String? in
print(element as Any)
return element
}

Filter function syntax?

This works:
func removeObject<T : Equatable>(object: T, array: [T]) -> Array<T>
{
return array.filter() { $0 != object }
}
let threeThings = ["one", "two", "three"]
twoThings = removeObject("three", threeThings)
However, I'd like to check for inequality with this !==. Then I get error "Type 'T' does not conform to protocol 'AnyObject'"
How can this code be fixed? (I've see the code here but I'd like to learn how to use filter properly).
The identical operator === and its negation !== are only defined for
instances of classes, i.e. instances of AnyObject:
func removeObject<T : AnyObject>(object: T, array: [T]) -> Array<T>
{
return array.filter() { $0 !== object }
}
=== checks if two variables refer to the same single instance.
Note that your code
let threeThings = ["one", "two", "three"]
twoThings = removeObject("three", threeThings)
does still compile and run, but gives the (perhaps unexpected) result
[one, two, three]
The Swift strings (which are value types and not class types) are automatically
bridged to NSString, and the two instances of NSString representing "three"
need not be the same.
If you want to use !== instead of !=, then, instead of the type constraint <T : Equatable> say <T : AnyObject>. All you have to do is listen to what the error message is telling you!
Note that this has nothing to do with using the filter function. It is simply a matter of types. You cannot use a method with an object of a type for which that method is not implemented. !== is implemented for AnyObject so if you want to use it you must guarantee to the compiler that this type will be an AnyObject. That is what the type constraint does.
!== checks for "identity", not "equality". Identity is a property of reference types which all support the AnyObject protocol, and it means that the two variables that you are comparing point to the same actual object, and not just another object with the same value.
That means you can't use === or !== with normal value types, like strings, integers, arrays, etc.
Try typing this into a playground:
let a = "yes"
let b = a
println(a === b)
You should get a message saying that String doesn't conform to the AnyObject protocol, because String is a value type (a struct) and doesn't support AnyObject, so you can't use === with it.
You can use === with any instance of a class, because classes are reference types, not value types.
You could put a constraint on T requiring that it conform to AnyObject.
func removeObject<T : Equatable where T: AnyObject>...
It should work, then, for reference types (including all class instances). But then your function won't work for value types (which include all structs).
Why do you need to check for identity (===) instead of equality (==)?

Comparing String Objects in Swift

Everyone knows you can use the == Operator to compare things.
if (stringValue1 == stringValue2)
If you do this in Objective-C the program will check if these objects are the same not if both strings do contain the same text. If you want to compare the text values you need to use a compare-Method.
To my understanding the same code in Swift does compare the text values. That is nice. A lot of programing language work like that. But what do I have to do to check if these two values refer to the same object?
For objects of class type you can you the === operator to check whether two objects refer to the same instance. However, you ask specifically about strings. Strings in swift are not of class type - they are values. The === operator will not work for them - the same way as it does not work for integers. So the answer to your question - how to check if two strings are the same instance - in Swift is: it is not possible. With strings in Swift you should use normal operators like == etc. only.
You can't as strings are values types, not object types. The === operator only works with object types (of AnyObject) but String is of type Any.
6> "abc" === "abc"
repl.swift:6:1: error: type 'String' does not conform to protocol 'AnyObject'
"abc" === "abc"
^
Swift.lhs:1:5: note: in initialization of parameter 'lhs'
let lhs: AnyObject?
^
6> var str : String = "abc"
str: String = "abc"
7> str === str
repl.swift:7:1: error: type 'String' does not conform to protocol 'AnyObject'
str === str
^
Swift.lhs:1:5: note: in initialization of parameter 'lhs'
let lhs: AnyObject?
^