I read in HackingWithSwift that Swift tuples can be compared with == operator only when the number of elements is less than or equal to 6. What is the reason behind this limitation ?
Background: Tuples aren't Equatable
Swift's tuples aren't Equatable, and they actually can't be (for now). It's impossible to write something like:
extension (T1, T2): Equatable { // Invalid
// ...
}
This is because Swift's tuples are structural types: Their identity is derived from their structure. Your (Int, String) is the same as my (Int, String).
You can contrast this from nominal types, whose identity is solely based off their name (well, and the name of the module that defines them), and whose structure is irrelevant. An enum E1 { case a, b } is different from an enum E2 { case a, b }, despite being structurally equivalent.
In Swift, only nominal types can conform to protocols (like Equatble), which precludes tuples from being able to participate.
...but == operators exist
Despite this, == operators for comparing tuples are provided by the standard library. (But remember, since there is still no conformance to Equatable, you can't pass a tuple to a function where an Equatable type is expected, e.g. func f<T: Equatable>(input: T).)
One == operator has to be manually be defined for every tuple arity, like:
public func == <A: Equatable, B: Equatable, >(lhs: (A,B ), rhs: (A,B )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, >(lhs: (A,B,C ), rhs: (A,B,C )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, >(lhs: (A,B,C,D ), rhs: (A,B,C,D )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, >(lhs: (A,B,C,D,E ), rhs: (A,B,C,D,E )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, F: Equatable>(lhs: (A,B,C,D,E,F), rhs: (A,B,C,D,E,F)) -> Bool { ... }
Of course, this would be really tedious to write-out by hand. Instead, it's written using GYB ("Generate your Boilerplate"), a light-weight Python templating tool. It allows the library authors to implement == using just:
% for arity in range(2,7):
% typeParams = [chr(ord("A") + i) for i in range(arity)]
% tupleT = "({})".format(",".join(typeParams))
% equatableTypeParams = ", ".join(["{}: Equatable".format(c) for c in typeParams])
// ...
#inlinable // trivial-implementation
public func == <${equatableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
${", ".join("lhs.{}".format(i) for i in range(1, arity))}
) == (
${", ".join("rhs.{}".format(i) for i in range(1, arity))}
)
}
Which then gets expanded out by GYB to:
#inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable>(lhs: (A,B), rhs: (A,B)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1
) == (
rhs.1
)
}
#inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2
) == (
rhs.1, rhs.2
)
}
#inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable>(lhs: (A,B,C,D), rhs: (A,B,C,D)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3
) == (
rhs.1, rhs.2, rhs.3
)
}
#inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable>(lhs: (A,B,C,D,E), rhs: (A,B,C,D,E)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3, lhs.4
) == (
rhs.1, rhs.2, rhs.3, rhs.4
)
}
#inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, F: Equatable>(lhs: (A,B,C,D,E,F), rhs: (A,B,C,D,E,F)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3, lhs.4, lhs.5
) == (
rhs.1, rhs.2, rhs.3, rhs.4, rhs.5
)
}
Even though they automated this boilerplate and could theoretically change for arity in range(2,7): to for arity in range(2,999):, there is still a cost: All of these implementations have to be compiled and produce machine code that ends up bloating the standard library. Thus, there's still a need for a cutoff. The library authors chose 6, though I don't know how they settled on that number in particular.
Future
There's two ways this might improve in the future:
There is a Swift Evolution pitch (not yet implemented, so there's no official proposal yet) to introduce Variadic generics, which explicitly mentions this as one of the motivating examples:
Finally, tuples have always held a special place in the Swift language, but working with arbitrary tuples remains a challenge today. In particular, there is no way to extend tuples, and so clients like the Swift Standard Library must take a similarly boilerplate-heavy approach and define special overloads at each arity for the comparison operators. There, the Standard Library chooses to artificially limit its overload set to tuples of length between 2 and 7, with each additional overload placing ever more strain on the type checker. Of particular note: This proposal lays the ground work for non-nominal conformances, but syntax for such conformances are out of scope.
This proposed language feature would allow one to write:
public func == <T...>(lhs: T..., rhs: T...) where T: Equatable -> Bool {
for (l, r) in zip(lhs, rhs) {
guard l == r else { return false }
}
return true
}
Which would be a general-purpose == operator that can handle tuples or any arity.
There is also interest in potentially supporting non-nominal conformances, allowing structural types like Tuples to conform to protocols (like Equatable).
That would allow one to something like:
extension<T...> (T...): Equatable where T: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
for (l, r) in zip(lhs, rhs) {
guard l == r else { return false }
}
return true
}
}
class TreeNode: Equatable {
static func ==(lhs: TreeNode, rhs: TreeNode) -> Bool {
lhs.val == rhs.val && lhs.left == rhs.right && lhs.right == rhs.left
}
var val: Int = 0
var left, right: TreeNode?
}
This code compiles and even works. But why? left and right variables are optional, isn't I supposed to unwrap it first in the body of static func ==?
Actually it isn't quite an equation. As you can see it's rather some sort of symmetrical equation. So I would like to define custom operator with different name for this purpose:
infix operator =|=: ComparisonPrecedence
class TreeNode {
static func =|=(lhs: TreeNode, rhs: TreeNode) -> Bool {
lhs.val == rhs.val && lhs.left =|= rhs.right && lhs.right =|= rhs.left
}
var val: Int = 0
var left, right: TreeNode?
}
And now it doesn't compile due to the reason I've mentioned earlier. It wants me to unwrap the optionals first.
Actually it would be great if it "just works" like in the case of "=="))) Because not having to unwrap the optionals explicitly would be convenient here.
So I want to understand why it behaves differently in these two situations.
This code compiles and even works. But why?
It is simply because there is an == operator declared for all Optional<Wrapped> where Wrapped is Equatable, like this:
static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool
TreeNode is Equatable in your first code snippet, so it works.
In your second code snippet, you haven't declared a =|= operator that operates on two TreeNode?. You can do that by either putting this in global scope...
func =|= (lhs: TreeNode?, rhs: TreeNode?) -> Bool {
switch (lhs, rhs) {
case (nil, nil): // both nil
return true
case (let x?, let y?): // both non-nil
return x =|= y // compare two non-optional tree nodes
default:
return false
}
}
or writing an Optional extension:
extension Optional where Wrapped == TreeNode {
static func =|= (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case (nil, nil): // both nil
return true
case (let x?, let y?): // both non-nil
return x =|= y // compare two non-optional tree nodes
default:
return false
}
}
}
But as Leo Dabus said, I'd just conform to Equatable and not create your own operator. Conforming to existing protocols allows you to use TreeNode with many APIs in the standard library, such as Array.contains(_:).
I have been trying to mix custom associated values with String in an Enum but not able to do so. When I try to apply a switch case over the enum, I get this error: Expression pattern of type 'Fruit' cannot match values of type 'Fruit'
Is it because Strings are value types and hence Swift is able to compare them but not custom class object of Fruit which is a reference type?
class Fruit{
let name: String?
let energyKcl: Double?
let costPerKg: Double?
init(name:String, energyKcl: Double, costPerKg: Double) {
self.name = name
self.energyKcl = energyKcl
self.costPerKg = costPerKg
}
}
enum Calorie {
case fruit(Fruit)
case chocolate (String)
case dairy(String)
case Nuts(String)
}
let banana = Fruit.init(name: "Banana", energyKcl: 100, costPerKg: 10)
func prepareBreakfast(calories: Calorie){
switch calories {
case .chocolate("Dark"):
print("Dark")
case .chocolate("White"):
print("White")
case .fruit(banana): //Error: Expression pattern of type 'Fruit' cannot match values of type 'Fruit'
print("banana")
default:
print ("Not available")
}
}
prepareBreakfast(calories: .fruit(banana))
No the problem is that custom class isn't comparable without Equatable protocol
extension Fruit: Equatable {
static func == (lhs: Fruit, rhs: Fruit) -> Bool {
return lhs.name == rhs.name
&& lhs.energyKcl == rhs.energyKcl
&& lhs.costPerKg == rhs.costPerKg
}
}
Pattern matching uses Equatable internally, so you should change your Fruit class:
extension Fruit: Equatable {
static func == (lhs: Fruit, rhs: Fruit) -> Bool {
return lhs.name == rhs.name // or every field if you want
}
}
If you want to use the reference, simply change the == func to return true if both references are equal, but I don't think it's a good idea:
static func == (lhs: Fruit, rhs: Fruit) -> Bool {
return lhs === rhs
}
In your code,
Replace the below line in prepareBreakfast(calories:) method,
case .fruit(banana):
with
case .fruit(let banana):
And you are good to go. I don't think there is any other issue with your code. It is working perfectly fine at my end.
class Test {
var count: Int;
init(count: Int) {
self.count = count;
}
}
extension Test: Comparable {
static func <(lhs: Test, rhs: Test) -> Bool {
return lhs.count > rhs.count
}
}
When I write this extension everything work okay but when i change < to > compiler error return
Type 'Test' does not conform to protocol 'Equatable'
Comparable extension required write < this function
What is this reason?
If you look at the docs for Comparable, you can see that it inherits from Equatable.
Equatable requires == to be implemented:
static func ==(lhs: Test, rhs: Test) -> Bool {
return lhs.count == rhs.count
}
I should also mention that count does not have an initial value. So you need to add an initializer for Test or a initial value to count.
EDIT:
If you look at the docs for Comparable, you'll find this bit:
Types with Comparable conformance implement the less-than operator (<)
and the equal-to operator (==). These two operations impose a strict
total order on the values of a type, in which exactly one of the
following must be true for any two values a and b:
a == b
a < b
b < a
So you must implement < and == but > is unnecessary. That's why it doesn't work when you only have >.
I created a class which is meant to be used as an "abstract class" (only to be subclassed, not instantiated directly). Since Swift doesn't support this, is has to be emulated using e.g. fatalError in body of abstract method.
My abstract class has to be equatable. So I thought, I use fatalError in equals method:
class MySuperClass:Equatable {
}
func ==(lhs: MySuperClass, rhs: MySuperClass) -> Bool {
fatalError("Must override")
}
class MySubClass:MySuperClass {
let id:Int
init(_ id:Int) {
self.id = id
}
}
func ==(lhs: MySubClass, rhs: MySubClass) -> Bool {
return lhs.id == rhs.id
}
let a = MySubClass(1)
let b = MySubClass(2)
let c = MySubClass(2)
a == b
b == c
And this works. I have the little problem though that my subclass has a type parameter. Now the example looks like this:
class MySuperClass:Equatable {
}
func ==(lhs: MySuperClass, rhs: MySuperClass) -> Bool {
fatalError("Must override")
}
class MySubClass<T>:MySuperClass {
let id:Int
init(_ id:Int) {
self.id = id
}
}
func ==<T>(lhs: MySubClass<T>, rhs: MySubClass<T>) -> Bool {
return lhs.id == rhs.id
}
let a = MySubClass<Any>(1)
let b = MySubClass<Any>(2)
let c = MySubClass<Any>(2)
a == b
b == c
And now it crashes, because it doesn't "see" the overriding equals, and it executes only equals in the superclass.
I know Swift has some issues concerning overrides using generic types. I thought though that this was limited to interaction with obj-c. This looks at least like a language deficiency, or bug, why would equals of generic class B not override equals of class A, if B is a subclass of A?
As Airspeed suggests the problem is that operator's implementation is not a part of class/struct implementation => hence, inheritance does not work there.
What you can do is to keep the logic inside of the class implementation and make operators use it. E.g. the following will do what you need:
class MySuperClass: Equatable {
func isEqualTo(anotherSuperClass: MySuperClass) -> Bool {
fatalError("Must override")
}
}
func == (lhs: MySuperClass, rhs: MySuperClass) -> Bool {
return lhs.isEqualTo(rhs)
}
class MySubClass<T>:MySuperClass {
let id: Int
init(_ id: Int) {
self.id = id
}
override func isEqualTo(anotherSuperClass: MySuperClass) -> Bool {
if let anotherSubClass = anotherSuperClass as? MySubClass<T> {
return self.id == anotherSubClass.id
}
return super.isEqualTo(anotherSuperClass) // Updated after AirSpeed remark
}
}
let a = MySubClass<Any>(1)
let b = MySubClass<Any>(2)
let c = MySubClass<Any>(2)
a == b
b == c
... as you can see == operator is defined only once and it uses MySuperClass's method to figure out whether its two arguments are equal. And after that .isEqualTo() handles the rest, including the use of inheritance mechanism on MySubClass level.
UPD
The benefit of above approach is that the following will still work:
let a2: MySuperClass = a
let b2: MySuperClass = b
let c2: MySuperClass = c
a2 == b2
b2 == c2
... i.e. regardless of the variable type at compile-time the behaviour will be determined by the actual instance type.
In overloading resolution, non-generic functions always have priority over generic ones, so the lesser rule that functions taking subclasses precede ones taking superclasses isn't taken into account.
A possible solution is to make the superclass == generic too. That way the rules for choosing between two generic functions kick in, and in this case the more specific one is the one taking specific classes parameterized by T:
func ==<T: MySuperClass>(lhs: T, rhs: T) -> Bool {
// bear in mind, this is a question of compile-time overloading,
// rather than overriding
fatalError("Must override")
}
func ==<T>(lhs: MySubClass<T>, rhs: MySubClass<T>) -> Bool {
return lhs.id == rhs.id
}
let a = MySubClass<Any>(1)
let b = MySubClass<Any>(2)
let c = MySubClass<Any>(2)
// no longer asserts
a == b
b == c