access swift enum case outside of switch? - swift

Let's say I have a simple Pet enum:
enum Pet {
case dog(name:String)
case cat(name:String)
var name:String {
switch self {
case .dog(let name):
return name
case .cat(let name):
return name
}
}
}
(The fact that they have the same type of associated value and therefore feels redundant should be ignored)
Now I want to be able to equate pets. I could write:
extension Pet:Equatable { }
func == (a:Pet, b:Pet) -> Bool {
return a.name == b.name
}
But this will allow a cat named "Spots" to be equal to a dog named "Spots". This I don't want. Is there an easy way to access the case of an enum instance? We usually do them in switch statements, but in this case, I wanted something more direct (one line), I might need to use it in a future Comparable extension after all.
I could do my own:
extension Pet {
var petType:Int {
switch self {
case .dog:
return 1
case .cat:
return 2
}
}
}
Now I can change my == implementation to be
func == (a:Pet, b:Pet) -> Bool {
return a.petType == b.petType && a.name == b.name
}
Is there some built in Swift syntax sugar that would do the same thing for me? I tried type(of:), but that just returns Pet, nothing about the instance case.

As far as I know, Swift does not provide us a shortcut to retrieve only case labels.
In your case you can write something like this utilizing your name property:
extension Pet: Equatable {
static func == (a: Pet, b: Pet) -> Bool {
switch (a, b) {
case (.dog, .dog), (.cat, .cat):
return a.name == b.name
default:
return false
}
}
}
If your Pet does not have the name property, you can write it as:
extension Pet: Equatable {
static func == (a: Pet, b: Pet) -> Bool {
switch (a, b) {
case let (.dog(namea), .dog(nameb)),
let (.cat(namea), .cat(nameb)):
return namea == nameb
default:
return false
}
}
}
But in your case, isn't it sort of natural to use class hierarchy?:
class Pet {
var name: String
init(name: String) {
self.name = name
}
}
class Dog: Pet {}
class Cat: Pet {}
extension Pet {
static func dog(name: String) -> Pet {
return Dog(name: name)
}
static func cat(name: String) -> Pet {
return Cat(name: name)
}
}
extension Pet: Equatable {
static func == (a: Pet, b: Pet) -> Bool {
return type(of: a) === type(of: b) && a.name == b.name
}
}

I have sort of a hybrid approach that keeps things a tad more modular:
enum Pet {
case dog(name:String)
case cat(name:String)
var name: String {
switch self {
case .dog(let name):
return name
case .cat(let name):
return name
}
}
func equalCaseTo(otherPet: Pet) -> Bool {
switch (self, otherPet) {
case (.dog(_), .dog(_)), (.cat(_), .cat(_)):
return true
default:
return false
}
}
func equalNameTo(otherPet: Pet) -> Bool {
return name == otherPet.name
}
}
func ==(left:Pet, right:Pet) -> Bool {
return left.equalCaseTo(right) && left.equalNameTo(right)
}

You can do something like this:
func ==(lhs: Pet, rhs: Pet) -> Bool {
if case let Pet.cat(l) = lhs, case let Pet.cat(r) = rhs where l == r { return true }
if case let Pet.dog(l) = lhs, case let Pet.dog(r) = rhs where l == r { return true }
return false
}
But a switch probably looks better and is definitely easier to maintain.

Related

Is there a Swift-er way to refer to an enum case as a 'type' that can by type-checked?

I'm trying to develop a parser that works off of Swift enums that represent tokens. I would like to be able to validate that a token is one of a certain type (e.g. in this case a certain case of an enum). Looking at the Swift docs, a way to do this without an additional enum isn't apparent to me, and I'm not optimistic. Is the following as close I can get to a more succinct pure Swift language-level solution?
enum SymbolTokenType {
case river, lake, mountain, meadow, valley, openParen, closeParen, logicalAnd, logicalOr, logicalNot
}
enum SymbolToken {
case river (key: RiverKey?)
case lake (key: LakeKey?)
case mountain (key: MountainKey?)
case meadow (key: MeadowKey?)
case valley (key: ValleyKey?)
case openParen
case closeParen
case logicalAnd
case logicalOr
case logicalNot
func validateTokenType(validTypes: [SymbolTokenType] ) -> Bool {
switch(self) {
case .river:
return validTypes.contains(.river)
case .lake:
return validTypes.contains(.lake)
case .mountain:
return validTypes.contains(.mountain)
case .meadow:
return validTypes.contains(.meadow)
case .valley:
return validTypes.contains(.valley)
case .openParen:
return validTypes.contains(.openParen)
case .closeParen:
return validTypes.contains(.closeParen)
case .logicalAnd:
return validTypes.contains(.logicalAnd)
case .logicalOr:
return validTypes.contains(.logicalOr)
case .logicalNot:
return validTypes.contains(.logicalNot)
}
}
}
I would do like this:
enum Token {
case token1
case token2
case token3
func isValid(from container: [Token]) -> Bool {
container.contains(self)
}
}
let validTokens: [Token] = [.token1, .token2]
let testedToken2 = Token.token2
let testedToken3 = Token.token3
testedToken2.isValid(from: validTokens) // True
testedToken3.isValid(from: validTokens) // False
-- UPD --
If enum have associated values, I would do like this:
enum Token {
case token1 (key: String?)
case token2 (key: String?)
case token3 (key: String?)
func isValid(from container: [Token]) -> Bool {
container.contains(self)
}
}
extension Token: RawRepresentable, Equatable {
typealias RawValue = Int
init?(rawValue: Int) {
switch rawValue {
case 1: self = .token1(key: nil)
case 2: self = .token2(key: nil)
case 3: self = .token3(key: nil)
default: return nil
}
}
var rawValue: Int {
switch self {
case .token1: return 1
case .token2: return 2
case .token3: return 3
}
}
// Equatable
static func == (lhs: Token, rhs: Token) -> Bool {
lhs.rawValue == rhs.rawValue
}
}
Result:
let validTokens: [Token] = [.token1(key: nil), .token2(key: nil)]
let testedToken2 = Token.token2(key: "Second token")
let testedToken3 = Token.token3(key: "Third token")
testedToken2.isValid(from: validTokens) // True
testedToken3.isValid(from: validTokens) // False
You definitely don't need two enums. You just have to deal with cases with associated values a bit differently.
protocol SimilarComparable: Equatable {
func isSimilar(to other: Self) -> Bool
func isSimilar(toAny others: [Self]) -> Bool
}
extension SimilarComparable {
func isSimilar(toAny others: [Self]) -> Bool {
let result = others.first(where: { $0.isSimilar(to: self) }) != nil
print("Finding similarity to \(self) in \(others) == \(result)")
return result
}
}
enum SymbolToken: SimilarComparable {
case river (key: String?)
case lake (key: String?)
case mountain (key: String?)
case meadow (key: String?)
case valley (key: String?)
case openParen
case closeParen
case logicalAnd
case logicalOr
case logicalNot
func isSimilar(to other: SymbolToken) -> Bool {
let result: Bool
print("Finding similarit to \(self) to \(other) == ", separator: " ", terminator: "")
switch (self, other) {
case (.river, .river):
result = true
case (.lake, .lake):
result = true
case (.mountain, .mountain):
result = true
case (.meadow, .meadow):
result = true
case (.valley, .valley):
result = true
default:
result = self == other
}
print("\(result)")
return result
}
}
Usage would be like this:
_ = SymbolToken.river(key: "Hudson").isSimilar(to: .river(key: "Nile")) // true
_ = SymbolToken.lake(key: "Michigan").isSimilar(to: .lake(key: "Tahoe")) // true
_ = SymbolToken.closeParen.isSimilar(to: .closeParen) // true
_ = SymbolToken.logicalOr.isSimilar(to: .logicalOr) // true
_ = SymbolToken.logicalOr.isSimilar(to: .logicalAnd) // false
let tokens: [SymbolToken] = [
.river(key: "Hudson"),
.lake(key: "Tahoe"),
.closeParen,
.logicalOr,
]
_ = SymbolToken.logicalOr.isSimilar(toAny: tokens) // true
_ = SymbolToken.lake(key: "Michigan").isSimilar(toAny: tokens) // true

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.

Swift: Protocol `static var foo: Self` and enums

Simplified example
Take a look at this simple protocol
protocol FooOwner {
static var foo: Self { get }
}
I would like an enum to conform to said protocol, and in my opinion this ought to work, since the static syntax SomeTypeConformingToFooOwner.foo should result in an instance of SomeTypeConformingToFooOwner and in the case where SomeTypeConformingToFooOwner is a enum.
enum Foo: FooOwner { // Type 'Foo' does not conform to protocol FooOwner
case foo
}
The workaround is this ugly thing:
protocol FooOwner {
static var fooOwner: Self { get }
}
enum Foo: FooOwner {
case foo
static var fooOwner: Foo {
return Foo.foo
}
}
Do you have a nicer workaround for enum conforming to protocols with static vars?
Real use case
protocol StringConvertibleError: Swift.Error {
static var invalidCharactersError: Self { get }
}
protocol StringConvertibleErrorOwner {
associatedtype Error: StringConvertibleError
}
protocol StringConvertible {
var value: String { get }
/// Calling this with an invalid String will result in runtime crash.
init(validated: String)
init(string value: String) throws
static func validate(_ string: String) throws -> String
}
// MARK: - Default Implementation Constrained
extension StringConvertible where Self: CharacterSetSpecifying, Self: StringConvertibleErrorOwner {
static func validate(_ string: String) throws -> String {
guard Self.allowedCharacters.isSuperset(of: CharacterSet(charactersIn: string)) else {
throw Error.invalidCharactersError
}
// Valid
return string
}
}
struct HexString: StringConvertible, CharacterSetSpecifying, StringConvertibleErrorOwner {
static var allowedCharacters = CharacterSet.hexadecimal
let value: String
init(validated unvalidated: String) {
do {
self.value = try HexString.validate(unvalidated)
} catch {
fatalError("Passed unvalid string, error: \(error)")
}
}
}
extension HexString {
enum Error: StringConvertibleError {
static var invalidCharactersError: Error {
return Error.invalidCharacters
}
case invalidCharacters
}
}
So it's the last part that I would like to change to:
extension HexString {
enum Error: StringConvertibleError {
case invalidCharacters
}
}
Since I have many similar types to HexString.
Yes of course obviously I can use one shared Error enum, but I would like to have one specific enum per type.
SE-0280 solves this - if accepted. It is currently in review.
Edit 1
Great news, SE-0280 has been accepted.
This might not fully match OP's issue, but as it seems somewhat related, I'm throwing it out there in case it helps.
In my case, I cared about some cases, but other cases could be ignored (or handled in a common way).
I admit my approach is not elegant and requires a lot of boiler plate, but it got me past a similar problem.
// Given these enums, we want a protocol that matches enums with
// 'foo' and 'bar' cases.
enum FooFriends {
case foo // conforms
case bar // conforms
case baz // don't really care
}
enum FooBar {
case foo // conforms
case bar // conforms
}
// We wish we could do this:
protocol FooBarWish {
case foo // or static var foo: Self { get }
case bar // or static var bar: Self { get }
}
// Workaround
// the boiler plate
enum FooBarProxy {
case foo
case bar
case other
}
protocol FooBarProtocol {
func getProxy() -> FooBarProxy
}
extension FooFriends: FooBarProtocol {
func getProxy() -> FooBarProxy {
switch self {
case .foo:
return .foo
case .bar:
return .bar
default:
return .other
}
}
}
extension FooBar: FooBarProtocol {
func getProxy() -> FooBarProxy {
switch self {
case .foo:
return .foo
case .bar:
return .bar
}
}
}
// Usage
// Instead of the ideal case (which won't work)
let fooBarOrFooFriend1: FooBarWish = FooFriends.foo
let fooBarOrFooFriend2: FooBarWish = FooBar.bar
// We can get by with
let fooBarProxy1 = FooFriends.foo.getProxy()
let fooBarProxy2 = FooBar.bar.getProxy()
// Verification
func checkIt(_ foobar: FooBarProxy) {
switch foobar {
case .foo:
print("it was foo")
case .bar:
print("it was bar")
case .other:
print("it was neither")
}
}
checkIt(fooBarProxy1)
checkIt(fooBarProxy2)
// =>
// it was foo
// it was bar

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

Filtering an array based on a Swift enum with an associated value - w/o mentioning the associated value

I have an enum with associated value for some of the cases:
enum Foo {
case a
case b
case c(String?)
}
I also have a struct with this enum as a variable
struct Bar {
var foo: Foo
}
I then have an array of this objects
let array:[Bar] = [Bar(foo: .a), Bar(foo: .c(nil)), Bar(foo: .c("someString"))]
I want to create a function that operates on a subset of this array, based on the cases it receives something like
func printOnly(objectsWithCase: Foo)
So far its pretty simple, but now here's the catch: for this operation I WANT TO IGNORE the associated value.
I would like to make this function be able to take .c case without mentioning an associated value, as if to say "give me the ones with .c regardless of the associated values".
I other words - I'd like to pass in something like .c(_) (this doesn't work of course) and have it return (print in this case) both Bar(foo: .c(nil)) and Bar(foo: .c("someString"))
So far, I only came up with changing the functions declaration to take the filtering closure instead of the cases like this:
func printArray(array: [Bar], condition: (Bar) -> Bool) {
let tmp = array.filter(condition)
print(tmp)
}
I'm wondering if there's a way to do this in Swift, while passing the cases and not the condition block ?
You can use the underscore as a wild card in pattern matching operations:
array.filter {
switch $0.foo {
case .a: return true // keep a
case .b: return false // reject b
case .c(_): return true // keep c, regardless of assoc. value.
}
}
While this is not technically what you ask for (I don't think there's any way to achieve this with enums), you can write a "fake" enum that contains a wildcard c that will match anything you want. This will give you the exact same syntax.
1) Replace Foo with the following
struct Foo: Equatable {
let rawValue: String
let associatedObject: String?
let isWildcard: Bool
fileprivate init(rawValue: String, associatedObject: String?, isWildcard: Bool) {
self.rawValue = rawValue
self.associatedObject = associatedObject
self.isWildcard = isWildcard
}
static var a: Foo {
return Foo(rawValue: "a", associatedObject: nil, isWildcard: false)
}
static var b: Foo {
return Foo(rawValue: "b", associatedObject: nil, isWildcard: false)
}
static var c: Foo {
return Foo(rawValue: "c", associatedObject: nil, isWildcard: true)
}
static func c(_ value: String?) -> Foo {
return Foo(rawValue: "c", associatedObject: value, isWildcard: false)
}
}
func ==(left: Foo, right: Foo) -> Bool {
// Match rawValue + associatedObject unless we have a wildcard
return (left.rawValue == right.rawValue)
&& (left.associatedObject == right.associatedObject || left.isWilcard || right.isWildcard)
}
2) Implement your printOnly function with ==
func printOnly(objects: [Bar], with match: Foo) {
objects.filter { $0.foo == match }.forEach { print($0) }
}
3) Success
printOnly(objects: array, with: .c) // [.c(nil), .c("someString")]
Discussion
The main drawback of this method, besides the additional boilerplate code, is that you are forced to create an enum value that should not be allowed. This method puts the responsibility on you to use it only as a wildcard, and not as a real enum value. It will also not guarantee you that other enum cases cannot be created, although you should be able to mitigate that by making the only initializer fileprivate.
Otherwise, this gives you exactly the same interface and features an enum would give you, you can define your cases just as before
let array = [Bar(foo: .a), Bar(foo: .c(nil)), Bar(foo: .c("Hello")]
Finally, you can still use it inside a switch, except you will always need to add a default statement.
switch Foo.c("Hello") {
case .a:
print("A")
case .b:
print("B")
case .c: // will match .c(nil) and .c("someString")
print("C")
default:
break
}
//: Playground - noun: a place where people can play
enum Foo {
case a
case b
case c(String?)
}
struct Bar {
var foo: Foo
}
let array:[Bar] = [Bar(foo: .a), Bar(foo: .c(nil)), Bar(foo: .c("someString"))]
func printArray(array: [Bar], condition: (Bar) -> Bool) {
let tmp = array.filter(condition)
print(tmp)
}
printArray(array: array) { bar in
switch bar.foo {
case .c:
return true
default:
return false
}
}
or
printArray(array: array) { bar in
if case let Foo.c = bar.foo {
return true
}
return false
}
EDIT
//: Playground - noun: a place where people can play
enum Foo: Equatable {
case a
case b
case c(String?)
}
func ==(lhs: Foo, rhs: Foo) -> Bool {
switch (lhs, rhs) {
case (.a, .a), (.b, .b), (.c, .c):
return true
default:
return false
}
}
struct Bar {
var foo: Foo
}
let array:[Bar] = [Bar(foo: .a), Bar(foo: .c(nil)), Bar(foo: .c("someString"))]
func printArray(array: [Bar], condition: (Bar) -> Bool) {
let tmp = array.filter(condition)
print(tmp)
}
func printOnly(objectsWithCase wantedCase: Foo) {
printArray(array: array) { bar in
if wantedCase == bar.foo {
return true
} else {
return false
}
}
}
printOnly(objectsWithCase:.c(nil))