Comparing optional arrays - swift

Running the following code snippet in the playground gives an error:
let a: [Int]? = [1,2]
let b: [Int]? = [1,2]
a == b // value of optional type '[Int]?' not unwrapped; did you mean to use '!' or '?'?
While doing something similar for a 'simpler' optional type works:
var x: Int? = 10
var y: Int?
x == y // false
What is the reasoning behind the first case, of optional arrays, not being allowed? Why can't Swift first see if either side if nil (.None) and then if they are not, do the actual array comparison.

The reason it works for simpler types is because there is a version of == that is defined for optionals that contain types that are Equatable:
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
But while Int is Equatable, Array is not (because it might contain something that is not equatable - in which case how could it be). All Equatable things have an == operator, but not all things with an == operator are Equatable.
You could write a special-case version of == specifically for optional arrays containing equatable types:
func ==<T: Equatable>(lhs: [T]?, rhs: [T]?) -> Bool {
switch (lhs,rhs) {
case (.Some(let lhs), .Some(let rhs)):
return lhs == rhs
case (.None, .None):
return true
default:
return false
}
}
You could also generalize this to cover any collection containing equatable elements:
func ==<C: CollectionType where C.Generator.Element: Equatable>
(lhs: C?, rhs: C?) -> Bool {
switch (lhs,rhs) {
case (.Some(let lhs), .Some(let rhs)):
return lhs == rhs
case (.None, .None):
return true
default:
return false
}
}

adding swift 3 version of Airspeed's answer:
func ==<T: Equatable>(lhs: [T]?, rhs: [T]?) -> Bool {
switch (lhs,rhs) {
case (.some(let lhs), .some(let rhs)):
return lhs == rhs
case (.none, .none):
return true
default:
return false
}
}
func ==<C: Collection where C.Iterator.Element: Equatable>(lhs: C?, rhs: C?) -> Bool {
switch (lhs,rhs) {
case (.some(let lhs), .some(let rhs)):
return lhs == rhs
case (.none, .none):
return true
default:
return false
}
}

Swift 4.1
Update: The missing functionality has been implemented in Swift 4.1.
Your code
let a: [Int]? = [1,2]
let b: [Int]? = [1,2]
a == b
compiles and works as expected since Xcode 9.3 (Swift 4.1).
Old answer
The easiest is not to use optional array and use an empty array ([]) instead of nil - in case you don't need to distinguish between those two cases.
let a = [1,2]
let b = [1,2]
let c = []
a == b
b != c
It worked in my case when I was writing Equatable extension for a struct. Instead of using property categories: [Category]? I have just changed it to categories: [Category] and parsed missing categories as empty array ([]).

Related

why multiple optionals(wrap nil) compare with operator "==" return false?

var opt1: Int??? = nil
var opt2: Int?? = nil
print(opt1 == opt2) // why is false?
At first I thought it was because of the different types(Int??? and Int??), so I custom a operator <==> and use the same code as the Optional' == source code to implement it:
extension Optional {
public static func <==> (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l <==> r
case (nil, nil): //I think it should match here
return true
default:
return false
}
}
}
print(opt1 <==> opt2) // false
(the source code link: https://github.com/apple/swift/blob/main/stdlib/public/core/Optional.swift)
I saw from the breakpoint that lhs and rhs become the same type.
In other words, this is not caused by the type.
And Optional' ~= operator implementation in the source code:
public static func ~=(lhs: _OptionalNilComparisonType, rhs: Wrapped?) -> Bool {
switch rhs {
case .some:
return false
case .none:
return true
}
}
According to the code above,I think case (nil, nil): in static func <==> should match, so print(opt1 == opt2) and print(opt1 <==> opt2) should be true, but why it's false?
There are four kinds of values that a Int??? can have:
.none (nil)
.some(.none) (a non nil Int??? wrapping a nil Int??)
.some(.some(.none)) (a non nil Int??? wrapping a non nil Int?? wrapping a nil Int?)
.some(.some(.some(n))) where n is an Int (an Int wrapped by 3 layers of Optionals)
Similarly, there are three kinds of values that an Int?? can have
.none
.some(.none)
.some(.some(n)) where n is an Int
When you write nil, it always means .none of whatever type that context needs, so both opt1 and opt2 are .none here.
What happens when you pass them to the == operator? Well, After some overload resolution/type inference, the compiler finds that == takes a two Int??? parameters, but you have passed an Int?? as the second argument. Luckily, there exists a conversion from any value t of type T to type T? - .some(t).
So after being passed into the ==, opt2 changes from .none to .some(.none), as you can see from this code snippet:
func test(lhs: Int???, rhs: Int???) {
if case .none = lhs, case .some(.none) = rhs {
print("lhs is .none and rhs is .some(.none)")
}
}
test(lhs: opt1, rhs: opt2) // prints
Hence they are "not equal".
The debugger seems to be showing both .none and .some(.none) as "nil", possibly because both of their debugDescription/description is "nil".
If you don't care about the multiple layers of optionals, you can just unwrap them to a single layer by doing as? Int, then compare:
print((opt1 as? Int) == (opt2 as? Int)) // true

Custom infix operators and optionals

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(_:).

Swift Enum: Expression pattern matching issue

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.

Why is Equatable not defined for optional arrays

Can someone give me a good reason for why this doesn't work:
let a: [Int]? = [1]
let b: [Int]? = nil
a == b
This would be my proposed (if inelegant) solution. But it's trivial, so I feel like I'm missing a good reason why this isn't implemented.
func ==<T: Equatable>(lhs: [T]?, rhs: [T]?) -> Bool {
if let lhs = lhs, let rhs = rhs {
return lhs == rhs
}
else if let _ = lhs {
return false
}
else if let _ = rhs {
return false
}
return true
}
Update: Conditional conformance has been implemented in Swift 4.1. Arrays and optionals of Equatable elements are themselves
Equatable now, and your code
let a: [Int]? = [1]
let b: [Int]? = nil
a == b
compiles and works as expected in Xcode 9.3. The workarounds are not
needed anymore.
(Old answer:)
Optionals can be compared only if the underlying wrapped type is equatable:
public func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
Now Arrays can be compared if the element type is equatable:
/// Returns true if these arrays contain the same elements.
public func ==<Element : Equatable>(lhs: [Element], rhs: [Element]) -> Bool
but even for equatable types T, Array<T> does not conform to the Equatable protocol.
At present, this is not possible in Swift, see for example
Why can't I make Array conform to Equatable? for a discussion
in the Apple developer forum. This change with the implementation
of SE-0143 Conditional conformances
in Swift 4.
Your implementation looks correct, here is a possible different one
using switch/case with pattern matching:
func ==<T: Equatable>(lhs: [T]?, rhs: [T]?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?) : // shortcut for (.Some(l), .Some(r))
return l == r
case (.None, .None):
return true
default:
return false
}
}

Swift error comparing two arrays of optionals

I get a compilation error in the next Swift code
var x:Array<Int?> = [1,2]
var y:Array<Int?> = [1,2]
if x == y { // Error
}
If both arrays are Array<Int> it works fine, but if at least one of them is optional it throws an error like the next:
Binary operator '==' cannot be applied to two Array<Int?> operands
I filed a bug report months ago but I had no answer. It still occurs in Swift 1.2.
Why is this happening?
The issue here is the distinction between something having an == operator, versus something being “equatable”.
Both Optional and Array have an == operator, that works when what they contain is equatable:
// if T is equatable, you can compare each entry for equality
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
// if T is equatable, you can compare the contents, if any, for equality
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
let i: Int? = 1
let j: Int = 1
i == j // fine, Int is Equatable
["a","b"] == ["a","b"] // and so is String
But they themselves do not conform to Equatable. This makes sense given you can put a non-equatable type inside them. But the upshot of this is, if an array contains a non-equatable type, then == won’t work. And since optionals aren’t Equatable, this is the case when you put an optional in an array.
You'd get the same thing if you tried to compare an array of arrays:
let a = [[1,2]]
let b = [[1,2]]
a == b // error: `==` can’t be applied to `[Array<Int>]`
If you wanted to special case it, you could write == for arrays of optionals as:
func ==<T: Equatable>(lhs: [T?], rhs: [T?]) -> Bool {
if lhs.count != rhs.count { return false }
for (l,r) in zip(lhs,rhs) {
if l != r { return false }
}
return true
}
For a counter-example, since Set requires its contents to be hashable (and thus equatable), it can be equatable:
let setarray: [Set<Int>] = [[1,2,3],[4,5,6]]
setarray == [[1,2,3],[4,5,6]] // true