Can you (now? Swift5?) write "if case" as a boolean when determining an enum's associated type? - swift5

Say you need to determine the actual associated type of an enum.
So, a situation like
enum MessageItem: Decodable {
case a(Images)
case b(Text)
case c(Reply)
...
}
I used to have code like this
xSome = x.filter {
switch $0 {
case .a(_):
return false
case .b(_):
return true
case .c(_):
return true
}
}
But then it was possible to have code like this
xSome = x.filter {
if case .a = $0 { return false }
return true
}
Is there now some way it Swift to compare against associated type producing a boolean?
So, something like:
xSome = x.filter {
return (case .a = $0)
}
So, something like anEnum.is( .someCase )
Is anything like this now in Swift?
(Naturally, I mean without adding a var in the enum, which of course you can do.)

Enums with associated values are equatable if you declare them Equatable. Here's the state of play:
enum MyEnum : Equatable {
case hey
case ho
case heyNonnyNo(String)
}
let e = MyEnum.hey
e == .hey // true
e == .ho // false
// e == .heyNonnyNo // blap, illegal
let e2 = MyEnum.heyNonnyNo("hello")
e2 == .heyNonnyNo("hello") // true
e2 == .heyNonnyNo("goodbye") // true
Why is e == .heyNonnyNo illegal? Because it's unclear what it can mean. This case has an associated value; its value is the associated value. So we can check whether two instances of this case have the same associated value, but we can't just ask (using ==) whether an instance is some associated value of this case.
So if that's what we want to know, we are back to if case:
if case .heyNonnyNo = e2 {
print("it's a hey nonny no")
}
But you can't say that without if (for use in a conditional) because if case is the keyword; case can't exist by itself. If you really need a Bool, you could write it out like this:
let ok : Bool = {
switch e2 {
case .heyNonnyNo: return true
default: return false
}
}()

Related

Is there a way in Swift to get an associated value without using a switch statement?

When I have a situation where I already know enum case statement I want to get the associated value of, is there a cleaner way than using a switch statement to pluck out the associated value?
To have to come up with a switch statement, provide multiple cases, or a default case just to extract the associated value is gaudy.
enum CircularReasoning {
case justPi(pi: Double)
case pizzaPie(howMany: Int)
}
var piInTheSky : Double
let whatLogic = CircularReasoning(pi: 3.1415926)
⬇️ 𝘸𝘒𝘯𝘡 𝘡𝘰 𝘒𝘷𝘰π˜ͺπ˜₯ ⬇️
switch whatLogic {
case .justPi(let pi):
piInTheSky = pi!
default:
break
}
You can use if case .<enum_case>(let value) as in TylerP's example,
or if case let .<enum_case>(value):
enum Foo {
case anInt(Int)
case aFloat(Float)
}
let aFoo: Foo = .anInt(9)
// Example of `if case .<enum_case)(let value)` syntax:
if case .anInt(let aValue) = aFoo {
print("aFoo = anInt(\(aValue))")
// Example of `if case let .enum_case(value)` syntax:
} else if case let .aFloat(aValue) = aFoo {
print("aFoo = aFloat(\(aValue))")
}
Both work. I'm not sure why the language includes both variants.
If you only care about one enum type, then either if syntax makes sense to me. If you are dealing with more than one possible enum value then the switch version seems cleaner.
Here's an adaptation of #DuncanC's excellent upvoted and accepted answer, as applied to a fictitious version of my real-world use case.
It illustrates a possible way to use his answer to reduce the space required to extract associated values, especially if one has a lot of one-off cases...
Note: Not implying this is appropriate or professional swift styling; it's clearly idiosyncratic, yet compact. (I usually don't compress things into one liners like this, unless they get really repetitive/redundant & produce lot of pointless vertical bloat).
enum RealmKey { case realmOfRealms, anyOldRealm, someOtherRealm }
.
.
.
enum SymbolToken {
case realm (key: RealmKey?)
case space (key: SpaceKey?)
case area (key: AreaKey?)
case region (key: RegionKey?)
case preserve (key: PeserveKey?)
case openParen
case closeParen
case logicalAnd
case logicalOr
case logicalNot
var realmKey : RealmKey? { if case .realm (let key) = self { return key } else { return nil } }
var spaceKey : SpaceKey? { if case .space (let key) = self { return key } else { return nil } }
var areaKey : AreaKey? { if case .area (let key) = self { return key } else { return nil } }
var regionKey : RegionKey? { if case .region (let key) = self { return key } else { return nil } }
var preserveKey : PreserveKey? { if case .preserve (let key) = self { return key } else { return nil } }
}
let realm = SymbolToken.realm(.realmOfRealms)
let realmKey = realm.realmKey

Is there anyway to provide default value to a boolean comparison involving optionals in Swift?

The logic behind optional is to give default action if the value is nil. I usually give default value in conditional evaluating as a substitute instead of force unwrap a variable, such as:
var test : Bool?;
test = resultOfSomeFunction();
if test ?? false { doThis(); } else { failMiserably(); }
Or
var name : String?
name = resultOfSomeFunction();
if (name ?? "") == "Plato" { youAreAPhilosopher(); } else { youAreOrdinaryMan(); }
Or
enum CarType { case alphard; case jaguar; case bwm; case mercedes; case none; }
var carType : CarType?;
carType = resultOfSomeFunction();
if (carType ?? .none) == .jaguar { rideThisCar(); } else { sellThisCar(); }
But for the last type, this kind of coding gets weird because I have to provide a special case .none in order for this default value to works. So I'm thinking, can somebody create comparison methods (==, <=, >=, <, >, !=, &&, ||) that can take optionals and return optional bool, and if any of the side of the operator is nil, the comparison will return nil? So I want to change the comparison into like this:
enum CarType { case alphard; case jaguar; case bwm; case mercedes; case none; }
var carType : CarType?;
carType = resultOfSomeFunction();
if (carType == .jaguar) ?? false { rideThisCar(); } else { sellThisCar(); }
I think this is a more straightforward way to read the code than the previous one, and this can really shorten some of my code:
if let startDate = objectStartDate,
let endDate = objectEndDate,
startDate < Date,
Date < endDate {
promotionIsActive();
}
else { promotionExpired(); }
Becomes:
if ((startDate < Date) && (Date < endDate)) ?? false {
promotionIsActive();
}
else { promotionExpired(); }
I have seen some code somewhere that tackle optional comparison, but it forces the result of the comparison into non-optional bool. I need the optional so I can provide my own default action. Thanks.

How to compare one value against multiple values - Swift

Let's say that you have the code
if stringValue == "ab" || stringValue == "bc" || stringValue == "cd" {
// do something
}
Is there a way to shorten this condition or beautify it (preferably without using the switch statement)? I know that this code does NOT work:
if stringValue == ("ab" || "bc" || "cd") {
// do something
}
I've seen some complex solutions on other languages, but they seem language specific and not applicable to Swift. Any solutions would be appreciated.
let valuesArray = ["ab","bc","cd"]
valuesArray.contains(str) // -> Bool
You can create an extension like this:
extension Equatable {
func oneOf(other: Self...) -> Bool {
return other.contains(self)
}
}
and use it like this:
if stringValue.oneOf("ab", "bc", "cd") { ... }
Credit for the impl which saved me typing it: https://gist.github.com/daehn/73b6a08b062c81d8c74467c131f78b55/
Not that i am aware, you can do something like this though,
let validStrings = Set<String>(arrayLiteral:"ab", "bc", "cd")
if validStrings.contains(str) {
//do something
}
Use a Switch Statement.
switch stringValue {
case "ab", "bc", "cd":
print("Yay!")
default:
break
}
The construction ["some", "array"].contains("value") works, but is somewhat annoying:
It inverts the left-to-right order you may want to write.
Items in the array are not declared using Swift's type inference, often forcing you to include unnecessary information to please the compiler.
You can instead use Set(["value"]).isSubset(of: ["some", "array"]).
The benefit is especially apparent when working with enums:
enum SomeReallyReallyLongTypeName {
case one, two
}
struct Thing {
let value: SomeReallyReallyLongTypeName
}
let thing = Thing(value: .one)
if Set([thing.value]).isSubset(of: [.one, .two]){
// :)
// Left-to-right order
// You get nice type inference
}
if [SomeReallyReallyLongTypeName.one, .two].contains(thing.value) {
// :(
// Annoying to have "SomeReallyReallyLongTypeName" in the code
}
if someArray.contains(object) {
// contains
} else {
// does not contains
}
The above function returns bool value, then you write logic accordingly.
Just for fun, how about overloading functions over String:
if a.isOneOf("ab", "bc", "cd") {
print("yes")
}
extension String {
#inlinable
func isOneOf(_ first: String, _ second: String) -> Bool {
self == first || self == second
}
#inlinable
func isOneOf(_ first: String, _ second: String, _ third: String) -> Bool {
self == first || isOneOf(second, third)
}
#inlinable
func isOneOf(_ first: String, _ second: String, _ third: String, _ fourth: String) -> Bool {
self == first || isOneOf(second, third, fourth)
}
}
This gives you full performance benefits, as the compiler will be able to inline and tail call as much as it wants, at the cost of having to write as many overloads as you need in your code, and also not being able to pass arrays - but other answers deal with this too.
let a = 1
let b = 1
let c = 1
let d = 1
if a == b,a==c,a==d {
print("all of them are equal")
}
else {
print("not equal")
}

How to compare enum with associated values by ignoring its associated value in Swift?

After reading How to test equality of Swift enums with associated values, I implemented the following enum:
enum CardRank {
case Number(Int)
case Jack
case Queen
case King
case Ace
}
func ==(a: CardRank, b: CardRank) -> Bool {
switch (a, b) {
case (.Number(let a), .Number(let b)) where a == b: return true
case (.Jack, .Jack): return true
case (.Queen, .Queen): return true
case (.King, .King): return true
case (.Ace, .Ace): return true
default: return false
}
}
The following code works:
let card: CardRank = CardRank.Jack
if card == CardRank.Jack {
print("You played a jack!")
} else if card == CardRank.Number(2) {
print("A two cannot be played at this time.")
}
However, this doesn't compile:
let number = CardRank.Number(5)
if number == CardRank.Number {
print("You must play a face card!")
}
... and it gives the following error message:
Binary operator '==' cannot be applied to operands of type 'CardRank' and '(Int) -> CardRank'
I'm assuming this is because it's expecting a full type and CardRank.Number does not specify an entire type, whereas CardRank.Number(2) did. However, in this case, I want it to match any number; not just a specific one.
Obviously I can use a switch statement, but the whole point of implementing the == operator was to avoid this verbose solution:
switch number {
case .Number:
print("You must play a face card!")
default:
break
}
Is there any way to compare an enum with associated values while ignoring its associated value?
Note: I realize that I could change the case in the == method to case (.Number, .Number): return true, but, although it would return true correctly, my comparison would still look like its being compared to a specific number (number == CardRank.Number(2); where 2 is a dummy value) rather than any number (number == CardRank.Number).
Edit: As Etan points out, you can omit the (_) wildcard match to use this more cleanly:
let number = CardRank.Number(5)
if case .Number = number {
// Is a number
} else {
// Something else
}
Unfortunately, I don't believe that there's an easier way than your switch approach in Swift 1.2.
In Swift 2, however, you can use the new if-case pattern match:
let number = CardRank.Number(5)
if case .Number(_) = number {
// Is a number
} else {
// Something else
}
If you're looking to avoid verbosity, you might consider adding an isNumber computed property to your enum that implements your switch statement.
Unfortunately in Swift 1.x there isn't another way so you have to use switch which isn't as elegant as Swift 2's version where you can use if case:
if case .Number = number {
//ignore the value
}
if case .Number(let x) = number {
//without ignoring
}
In Swift 4.2 Equatable will be synthesized if all your associated values conform to Equatable. All you need to do is add Equatable.
enum CardRank: Equatable {
case Number(Int)
case Jack
case Queen
case King
case Ace
}
https://developer.apple.com/documentation/swift/equatable?changes=_3
What I usually do to compare if two enum cases "match" no matter their associated value is:
I have a protocol Matchable:
protocol Matchable {
static func ~= (lhs: Self, rhs: Self) -> Bool
}
Then I make enums conform to it:
extension CardRank: Matchable {
static func ~= (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case
(.number, .number),
(.jack, .jack),
(.queen, .queen),
(.king, .king),
(.ace, .ace):
return true
default:
return false
}
}
}
let card1: CardRank = .number(1)
let card2: CardRank = .number(2)
let card3: CardRank = .jack
print(card1 ~= card2) // true
print(card1 ~= card3) // false
Here's a simpler approach:
enum CardRank {
case Two
case Three
case Four
case Five
case Six
case Seven
case Eight
case Nine
case Ten
case Jack
case Queen
case King
case Ace
var isFaceCard: Bool {
return (self == Jack) || (self == Queen) || (self == King)
}
}
There's no need to overload the == operator, and checking for card type does not require confusing syntax:
let card = CardRank.Jack
if card == CardRank.Jack {
print("You played a jack")
} else if !card.isFaceCard {
print("You must play a face card!")
}
I didn't want to conform Equatable (it didn't help me either) and I wanted to filter for other cases than a specific one, so instead of simply writing card != .Number I had to write the following. (I adjusted my code to this question.)
enum CardRank {
...
var isNumber: Bool {
if case .Number = self { return true }
return false
}
}
So I can write not a number in a complex condition:
if something && !card.isNumber { ... }
I wish I could just write card != .Number, but the compiler was always complaining with Type of expression is ambiguous without more context. Maybe in an upcoming swift version!
You don't need func == or Equatable. Just use an enumeration case pattern.
let rank = CardRank.Ace
if case .Ace = rank { print("Snoopy") }
extension CardRank {
func isSameCaseAs(_ other: CardRank) -> Bool {
switch (self, other) {
case (.Number, .Number),
(.Jack, .Jack),
(.Queen, .Queen),
(.King, .King),
(.Ace, .Ace):
return true
default:
return false
}
}
}
let number = CardRank.Number(1)
let otherNumber = CardRank.Number(2)
number.isSameCaseAs(otherNumber) // true
Just create an extension and ignore the associated types.
From Swift 5.3, you can use the Comparable Enums feature:
enum CardRank: Comparable {
case Number(Int)
case Jack
case Queen
case King
case Ace
}
let cards: [CardRank] = [
.Queen, .Number(8), .Ace, .Number(3), .King
]
print(cards.sorted())
// [.Number(3), .Number(8), .Queen, .King, .Ace]

How would I create a constant that could be one of several strings depending on conditions?

I want to have a constant using let that may be one of several values.
For instance:
if condition1 {
constant = "hi"
}
else if condition2 {
constant = "hello"
}
else if condition3 {
constant = "hey"
}
else if condition4 {
constant = "greetings"
}
I'm not sure how to do this with Swift and the let feature. But I'm inclined to believe it's possible, as this is in the Swift book:
Use let to make a constant and var to make a variable. The value of a constant doesn’t need to be known at compile time, but you must assign it a value exactly once.
How would I accomplish this?
As pointed out in the other answers you can't directly do this. But if you're looking to just variably set the initial value of a constant, then yes, that is possible. Here's an example with a computed property.
class MyClass {
let aConstant: String = {
if something == true {
return "something"
} else {
return "something else"
}
}()
}
I think you are looking for variable which will be assigned later inside switch-case:
let constant :String
switch conditions {
case condition1:
constant = "hi"
case condition2:
constant = "hello"
case condition3:
constant = "hey"
case condition4:
constant = "greetings"
default:
constant = "salute"
}
One option would be something like this, using a closure:
let constant: String = ({ value in
if conditionOne {
return "Hi"
} else if conditionTwo {
return "Bye"
}
return "Oops!"
})(myData /*needed for condition*/)
Or, for another twist, using generics:
func fancySwitch<S, T>(val: S, fn: S -> T) -> T {
return fn(val)
}
let x: String = fancySwitch(3) { val in
if val == 2 {
return "Hi"
} else if val < 5 {
return "Bye"
}
return "Oops"
}
let y: String = fancySwitch((3, 4)) { (a, b) in
if a == 2 {
return "Hi"
} else if b < 5 {
return "Bye"
}
return "Oops"
}
I understand what you're looking for. In Scala and some other functional languages this can be done using the match statement (kind of like switch) because the entire statement resolves to a value like this:
val b = true
val num = b match {
case true => 1
case false => 0
}
This is unfortunately not directly possible in Swift because there is no way to get a value from a branch statement. As stated in the Swift book, "Swift has two branch statements: an if statement and a switch statement." Neither of these statements resolve to a value.
The closest code structure I can think of is to first use a variable to retrieve the correct value and then assign it to a constant to be used in any later code:
let b = true
var num_mutable: Int
switch b {
case true:
num_mutable = 1
default:
num_mutable = 0
}
let num = num_mutable
Just add the line let constant: String before your if/else statement.
Below, an excerpt from Swift 1.2 and Xcode 6.3 beta - Swift Blog - Apple Developer elaborates.
let constants are now more powerful and consistent β€” The new rule is
that a let constant must be initialized before use (like a var), and
that it may only be initialized, not reassigned or mutated after
initialization. This enables patterns like:
let x : SomeThing
if condition {
x = foo()
} else {
x = bar()
}
use(x)
This formerly required the use of a var even though there is no
mutation taking place. Properties have been folded into this model to
simplify their semantics in initializers as well.
I found the Swift blog post above from the article "Let It Go: Late Initialization of Let in Swift", which I found by googling: swift let constant conditional initialize.