Swift Enum: Expression pattern matching issue - swift

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.

Related

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

Comparing two enum variables regardless of their associated values

Consider this enum:
enum DataType {
case One (data: Int)
case Two (value: String)
}
Swift has pattern matching to compare an enum with associated values, like so:
let var1 = DataType.One(data: 123)
let var2 = DataType.One(data: 456)
if case DataType.One(data: _) = var2 {
print ("var2 is DataType.One")
}
How would one go about comparing not one variable against an enum type, but comparing the enum type of two variables? I saw a ton of similar questions, but none focused on the case where you have two variables.
What I basically want is:
if case var1 = var2 {
print ("var1 is the same enum type as var2")
}
Updated approach:
I think there's no native support for this. But you can achieve it by defining a custom operator (preferrably by using a protocol, but you can do it directly as well). Something like this:
protocol EnumTypeEquatable {
static func ~=(lhs: Self, rhs: Self) -> Bool
}
extension DataType: EnumTypeEquatable {
static func ~=(lhs: DataType, rhs: DataType) -> Bool {
switch (lhs, rhs) {
case (.one, .one),
(.two, .two):
return true
default:
return false
}
}
}
And then use it like:
let isTypeEqual = DataType.One(value: 1) ~= DataType.One(value: 2)
print (isTypeEqual) // true
Old approach:
protocol EnumTypeEquatable {
var enumCaseIdentifier: String { get }
}
extension DataType: EnumTypeEquatable {
var enumCaseIdentifier: String {
switch self {
case .one: return "ONE"
case .two: return "TWO"
}
}
}
func ~=<T>(lhs: T, rhs: T) -> Bool where T: EnumTypeEquatable {
return lhs.enumCaseIdentifier == rhs.enumCaseIdentifier
}
The older version depends on Runtime and might be provided with default enumCaseIdentifier implementation depending on String(describing: self) which is not recommended. (since String(describing: self) is working with CustromStringConvertible protocol and can be altered)
Just confirm to Equatable like below
extension DataType: Equatable {
static func == (lhs: DataType, rhs: DataType) -> Bool {
switch (lhs, rhs) {
case (.One, .Two), (.Two, .One):
return false
case (.One, .One), (.Two, .Two):
return true
}
}
}
If you don't want to implement Equatable just move content into instance method:
extension DataType{
func isSame(_ other: DataType) -> Bool {
switch (self, other) {
case (.One, .Two), (.Two, .One):
return false
case (.One, .One), (.Two, .Two):
return true
}
}
}
Use:
let isTypeEqual = DataType.One(value: 1).isSame(DataType.One(value: 2))
print (isTypeEqual) // true
This worked for me:
enum DataType {
case one (data: Int)
case two (value: String)
}
protocol EnumTypeEquatable {
static func sameType(lhs: Self, rhs: Self) -> Bool
}
extension DataType: EnumTypeEquatable {
static func sameType(lhs: DataType, rhs: DataType) -> Bool {
if let caseLhs = Mirror(reflecting: lhs).children.first?.label, let caseRhs = Mirror(reflecting: rhs).children.first?.label {
return (caseLhs == caseRhs)
} else { return false }
}
}
let isTypeEqual = DataType.sameType(lhs: .one(data: 1), rhs: .one(data: 2))
print (isTypeEqual) // true

Comparing optional arrays

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 ([]).

Enum variables in Swift?

I would like to associate multiple values with an enum value, in a generic way.
This can be done in Java:
enum Test {
A("test", 2);
final String var1;
final int var2;
Test (String var1, int var2) {
this.var1 = var1;
this.var2 = var2;
}
}
public static void main(String []args){
Test test = Test.A;
System.out.println(test.var1);
}
But it looks like it's not possible with Swift? So far, according to docs, there are:
Associated values. Example (from docs):
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}
But this is not what I need.
Raw value. Example (from docs):
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
This would be what I need, but it can have only one value!
Is there an elegant solution for this...? Seems like a language design decision, as it would conflict with the associated values concept, at least in the current form. I know I could use e.g. a dictionary to map the enum values to the rest, but really missing to do this in one safe step, like in Java.
For Swift enum, you can only use (String|Integer|Float)LiteralConvertible types as the raw value. If you want to use existing type(e.g. CGPoint) for the raw value, you should follow #Alex answer.
I will provide 2 alternatives in this answer
Very simple solution
enum Test: String {
case A = "foo:1"
case B = "bar:2"
var var1: String {
return split(self.rawValue, { $0 == ":" })[0]
}
var var2: Int {
return split(self.rawValue, { $0 == ":" })[1].toInt()!
}
}
let test = Test.A
println(test.var1) // -> "foo"
You don't like this? go to next one :)
Behavior emulation using struct and static constants
struct Test {
let var1: String
let var2: Int
private init(_ var1:String, _ var2:Int) {
self.var1 = var1
self.var2 = var2
}
}
extension Test {
static let A = Test("foo", 1)
static let B = Test("bar", 2)
static let allValues = [A, B]
}
let test = Test.A
println(test.var1) // -> "foo"
But of course, struct lacks some features from enum. You have to manually implement it.
Swift enum implicitly conforms Hashable protocol.
extension Test: Hashable {
var hashValue:Int {
return find(Test.allValues, self)!
}
}
func ==(lhs:Test, rhs:Test) -> Bool {
return lhs.var1 == rhs.var1 && lhs.var2 == rhs.var2
}
Test.A.hashValue // -> 0
Test.B.hashValue // -> 1
Test.A == Test.B // -> false
In the first code, we already have allValues that is corresponding to values() in Java. valueOf(...) in Java is equivalent to init?(rawValue:) in RawRepresentable protocol in Swift:
extension Test: RawRepresentable {
typealias RawValue = (String, Int)
init?(rawValue: RawValue) {
self.init(rawValue)
if find(Test.allValues, self) == nil{
return nil
}
}
var rawValue: RawValue {
return (var1, var2)
}
}
Test(rawValue: ("bar", 2)) == Test.B
Test(rawValue: ("bar", 4)) == nil
And so on...
I know this is not "in a generic way". And one thing we never can emulate is "Matching Enumeration Values with a Switch Statement" feature in Swift. you always need default case:
var test = Test.A
switch test {
case Test.A: println("is A")
case Test.B: println("is B")
default: fatalError("cannot be here!")
}
Yes it is a design decision but you can kind of work around it in some cases.
The idea is to extend a Type to conform to one of:
integer-literalĀ­ floating-point-literalĀ­ string-literal
The solution can be found here
Bryan Chen's solution:
How to create enum with raw type of CGPoint?
The second solution presented there by Sulthan may also be a way to go for you.
I'm not familiar enough with Swift's history to know if this was possible back when the question was asked. But this is what I would do today in Swift 5.x:
enum Direction {
case north
case east
case south
case west
func name() -> String {
switch self {
case .north: return "North"
case .east: return "East"
case .south: return "South"
case .west: return "West"
}
}
func degress() -> Double {
switch self {
case .north: return 0.0
case .east: return 90.0
case .south: return 180.0
case .west: return 270.0
}
}
}
It retains all the benefits of Swift enums, chief of all, IMO, the ability for the compiler to infer when your code is exhaustive when pattern matching.

Error why matching an enumeration using a if statement [duplicate]

This question already has an answer here:
Testing for enum value fails if one has associated value?
(1 answer)
Closed 8 years ago.
I face a problem using enumeration I can't understand.
Here is declaration of an enumeration type:
enum SomeType {
case un
case deux
case trois
}
Then I want to match an individual enumeration values with a if statement:
var testValue: SomeType = .trois
if testValue == .trois {
// Do something
}
Everything is fine!
Now I want to add an associated value only to the first member value:
enum SomeType {
case un(Int)
case deux
case trois
}
var testValue: SomeType = .trois
if testValue == .trois {
// Do something
}
An error than appear on the if statement: Could not find member 'trois'
Does this mean enumerations can only be matched using a switchstatement?
Precisions
What I want to achieve is: "Does testValue is of member value of 'trois' with no consideration for associated value". In others words, how to match an enumeration on member value only.
Here a solution implementing Airspeed Velocity answers:
// Test equality only on member value
func == (lhs:SomeType, rhs:SomeType) -> Bool {
switch (lhs, rhs) {
case (.un(let lhsNum), .un(let rhsNum)):return true
case (.deux, .deux): return true
case (.trois, .trois): return true
default: return false
}
}
// Test equality on member value AND associated value
func === (lhs:SomeType, rhs:SomeType) -> Bool {
switch (lhs, rhs) {
case (.un(let lhsNum), .un(let rhsNum)) where lhsNum == rhsNum: return true
case (.deux, .deux): return true
case (.trois, .trois): return true
default: return false
}
}
var testValue = SomeType.un(3)
// Tests
if testValue == .un(1) {
println("Same member value")
}
if testValue === .un(3) {
println("Same member value AND same associated contents")
}
Enums that don't have associated types are automatically equatable. Enums that have associated types aren't. This makes sense, because only you can know how the associated type (such as the integer that comes with your .un value) should be handled. Even though .trois doesn't have an associated type, the lack of freebie equateableness affects the whole enum. Switch works a little differently, using pattern matching, so it still works.
If you want your enum with an associated type to be equatable, you can define your own == operator:
enum SomeType {
case un(Int)
case deux
case trois
}
// possibly there's a more succinct way to do this switch
func ==(lhs: SomeType, rhs: SomeType) -> Bool {
switch (lhs,rhs) {
case let (.un(i), .un(j)) where i == j: return true
case (.deux,.deux): return true
case (.trois, .trois): return true
default: return false
}
}
var testValue: SomeType = .trois
if testValue == .trois {
println("equals .trois")
}
// note, for SomeType to work with generic
// functions that require Equatable, you have
// to add that too:
extension SomeType: Equatable { }
// which means this will work:
let a: [SomeType] = [.un(1), .deux, .trois]
find(a, .trois)