Compare two objects in Swift without casting to a specific type - swift

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.).

Related

Swift Generics - return equals if they are Equatable, or return nil

Is there a way to check if a generic type conforms to Equatable or not? I would like to be able to check if two objects of the same generic type are equal or not, or if equality has no meaning for them.
As Equatable can only be used as a generic constraint (because it has Self or associatedType requirements) I have tried using generic overloading:
//If T is equatable, this is more specific so should be called
func equals<T:Equatable>(lhs:T, rhs:T) -> Bool?{
return lhs == rhs
}
//This should only be called if T is not equatable at compile time
func equals<T>(lhs:T, rhs:T) -> Bool?{
return nil
}
This works when called with a specific type, e.g. equals(lhs:1, rhs:1) returns true as expected. However, if it is called in a generic context it always returns nil:
func doSomethingThenCheckEquals<T>(lhs:T, rhs:T){
//Do something here which has no type requirements
//Check if the two objects are equal - would usually do something with the result
//This will always use equals<T> and never equals<T:Equatable>, so will always be nil
_ = equals(lhs:lhs, rhs:rhs)
}
Is there some way to achieve the desired result?
Further, according to this answer, the compiler starts with a single implementation with dynamic type checking but can in some cases create specialized implementations. In the case that the compiler created a specialized implementation, would it behave similarly to the first case (where equals(lhs:1, rhs:1) returns true)?
The compiler is doing its job as expected. The second method which you have declared is independent of the first and so it doesn't know anything about T.
Generic is forward declaration which means we need to tell the compiler what protocol it will follow and then the compiler will take all necessary step to fit this in. Maybe in future, we can expect function call level type interpretation but currently, it is not available.
func doSomethingThenCheckEquals<T>(lhs:T, rhs:T){
//Do something here which has no type requirements
//Check if the two objects are equal - would usually do something with the result
//This will always use equals<T> and never equals<T:Equatable>, so will always be nil
_ = equals(lhs:lhs, rhs:rhs)
}
The best solution would be to use where clause.
func doSomethingThenCheckEqual<T>(lhs: T, rhs: T) where T:Equatable {
}
Read more about it here.

The difference between an any type and a generic type in swift

What is the difference between an any type and generic type in swift?
Any Type Example:
let swiftInt: Int = 1
let swiftString: String = "miao"
var array: [Any] = []
array.append(swiftInt)
array.append(swiftString)
Generic Type Example:
func duplicate<T>(item: T, numberOfTimes n: Int) -> [T] {
var buffer : [T] = []
for _ in 0 ..< n {
buffer.append(item)
}
return buffer
}
Is this a matter of preference because both appear to solve the same problem by being able to substitute the desired type.
I'm not going to explain generics in details and i'll just point out the essential differences.
In the first example, you'll be able to append any type in that array, without being able to restrict beforehand your array to a specific type and to leverage compile time checks to guarantee that the array will not contain extraneous types. Not much to see in that example.
The second example contains instead a generic function that provides all of the above functionalities, consistency checks on the content of the array will come for free and if you want you'll also be able to specify additional characteristics of that generic type T, like requesting that it implements a specific protocol (e.g. limit duplicate() to object that implement Comparable or Equatable).
But that is just a simple example of a generic function, you can also have parametrized classes (what you'll use the most) and there are a lot of additional functionalities.
Never use Any as a poor-man generics, real generics are way more flexible, add useful checks and make more explicit your intentions, with minimal additional effort required to implement them.
Any means "I don't want any type checking and I won't be able to call type-specific methods without casting"
For example, try to call:
var array: [Any] = [1, 2]
var sum = array[0] + array[1] // you cannot do this! you have to cast to Int first
A generic type is a placeholder for a type. When used, a concrete type is used instead of it (e.g. an Int or a String).
In short, never use Any. There are very very few specific situations when Any is what you want to use.

Swift generic constraints based on operator

Suppose I want to add up all the values of an entry of an array. Not only integers, but also double values or some type I created myself which implements the + operator. So my question is: Is it possible to create such a function with a constraint that is based on the fact if the type implements the operator? Such as or something like that (obviously THIS isn't working).
Thanks in advance
There are several ways you can approach the problem.
IntegerArithmeticType
While I know of no explicit way to say "Hey Swift, I want to only allow type T where T has this operator", all types we commonly think of as capable of being added, subtracted, multiplied, and divided automatically conform to IntegerArithmeticType, which can be used as a generic constraint for any generic function or type.
For example:
func addAll<T: IntegerArithmeticType>(array: [T]) -> T {
var count = array[0]
for (index, value) in array.enumerate() {
if index != 0 {
count += value
}
}
return count
}
Notice in this quick mock-up version I initialized count with the first value of the array and then avoiding double-counting by checking the index against 0 inside the for loop. I can't initialize count with 0 because 0 is an Int while I want count to be of type T.
You mentioned having your own types work with this too. One option is to have your custom type conform to IntegerArithmeticType, although it requires a lot more than just the + operator. For details on IntegerArithmeticType, see SwiftDoc.
Custom Protocol
If conforming to IntegerArithmeticType imposes some sort of limitation on you, you can create a custom protocol with the + function and whatever other requirements you would like. After all, operators are really just functions. Note that you can add conformance to already existing types with extensions. For example:
protocol Addable {
func +(left: Self, right: Self) -> Self
// Other requirements...
}
extension Int: Addable {}
extension Double: Addable {}

Functions on generic arrays

I would like to use more functional programming in Swift. Some of the functions I write could work well on Arrays of various types. I don't want to rewrite the same function with different types (or typealiases.) The pattern how the function would work is often the same, just with different types. So, I tried something like this:
// Add indeces to an array of any type. I.e., returns an array of tuples of the array index and the original element.
func addIndeces<T: AnyObject>(toArray: Array<T>) -> Array<(index: Int, value: T)> {
var arrIndex: [Int] = []
for index in 0...toArray.count {
arrIndex.append(index)
}
return Array(Zip2(arrIndex, toArray))
}
When I call this function
// Note: reminderList is of type [Reminder]
let indexedReminderList = addIndeces(reminderList) as! [(index: Int, reminder: Reminder)]
I get a runtime error: "fatal error: can't unsafeBitCast between types of different sizes"
What am I doing wrong?
The function you are writing already exists – kind of. enumerate "return a lazy SequenceType containing pairs (n, x), where n\ s are consecutive Int\ s starting at zero, and x\ s are the elements of base"
This means you can write your function as:
func addIndices<T>(toArray: [T]) -> [(index: Int, reminder: T)] {
// if you want to label the tuple elements index: and reminder:,
// you still have to use map:
return map(enumerate(toArray)) {
(index: $0, reminder: $1)
}
}
Note, you don’t need to write T: AnyObject unless you specifically want to prevent this function from accepting anything other than arrays of classes (as that's what AnyObject is - the protocol that only classes, not structs or enums, conform to).
Note, enumerate only works for integer-indexed collections. To make it more general, you could write zip(indices(someCollection),someCollection)). indices returns a range of all the indices of any collection, so is equivalent to someCollection.startIndex..<someCollection.endIndex.
You want to cast the tuple Array of type [(index: Int, value: T)] where T is of type Reminder to a tuple Array of type [(index: Int, reminder: Reminder)]. So you can see that the tuples have different element names (value and reminder) where both have different byte sizes - therefore the error.
So you should take the same element names for both tuples.
If you want to change the element names:
let newTupleArray = oldTupleArray.map{ (newElementName0: $0.0, newElementName1: $0.1) }
The range 0...toArray.count consist all elements form 0 to the count of toArray, including the count value, and in your case arrIndex will always have one more element then toArray. Thats what is causing different size error. To fix it replace your range with this one 0..<toArray.count

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 (==)?