If clauses allowing comparison for one item to multiple others

What if you could create an if clause that compares an item to multiple other items, like this:

if response == ("y" || "yes") {
    // Actions
} else {
    // Other Actions
}

if item == (item1 && item2 && item3 && item4 && item5) {
    // More Actions
}

This would save a lot of time and code, being able to compare multiple items together in a more compact method. This should work with anything that's Equatable or Comparable.

I have a working version of the Equatable side of this, if this get's accepted I can make Comparable work, too.

First I want to ask one question, how does && work? It is likely to be true only if all items are the same, but the way it's written, I don't think it represents.

Now, when we solve the first problem in Swift today, we can use contains:

if [item1, item2, ...  ].contains(item) {}

There is another way:

switch item {
case item1, item2, ...  : ...
default: break
}

It's longer than the if statement, but switch is far more powerful. It's because it uses the power of pattern matching.

You can use pattern matching in if statements as well. But only one. You can't enumerate them.

I would suggest extending the if case to match multiple values. This example is also an example of

if case item1, item2, ...  = item {}

This is shorter than using switch and can be used more often than contains You can do this. Of course you can use if case let as well. I'd also like to apply this extension to guard and while. I believe it's necessary to improve the expressiveness of Swift.

First I want to ask one question, how does && work?

What I was thinking is that if item == (item1 && item2 && item3 && item4 && item5) would be a shorter version of if item == item1 && item == item2 && item == item3...

I would suggest extending the if case to match multiple values.

I agree that would work for ||.

How about Comparable, any ideas that make more sense than if number > (number1 && int2 && int3)

I don't think it should be from the list of valid, customizable operators, like && or ||. Users can easily interfere with its behavior. It's already ambiguous with Bool values.

6 Likes

In some languages, you can write item == item1 == item2 == item3 as It can be done. You can't do this in type-safe Swift, but I think it's easier to understand than your example.

I don't expect this kind of thing to be adopted very often, but personally, I'm more likely to write without &&. I think it's easy to understand. For example, this is what it looks like.

precedencegroup Group {
	higherThan: DefaultPrecedence
}

infix operator &==: Group
func &==<T: Equatable>(lhs: T, rhs: T?) -> T? {
	switch rhs {
	case .none: 
		return nil
	case .some(let v):
		return v == lhs ? v : nil
	}
}

infix operator ==
func ==<T: Equatable>(lhs: T, rhs: T?) -> Bool {
	switch rhs {
	case .none: 
		return false
	case .some(let v):
		return v == lhs
	}
}

if 1 == 1 &== 1 {
	print(1)  // prints "1"
}
if 0 == 1 &== 2 {
	print(2)  // doesn't print
}

As for your question, you can write this using the max and min functions.

  • if number > (number1 && int2 && int3)
// you can write:
if number > max(number1, number2, number3)
  • if number > (number1 || int2 || int3)
// you can write:
if number > min(number1, number2, number3)

If this is the only goal you want to achieve, then there's already a way to do it with a very comparable amount of keystrokes:

if ["y", "yes"].contains(response) { ... }

or

if [item, item2, item3, item4, item5].allSatisfy { $0 == item } { ... }

Granted, this might have less resemblance with the common maths notation, but writing such predicates in terms of usual Sequence methods also appears less "magical" to the reader I think.

1 Like

Swift is a flexible language already. :slight_smile:

infix operator ∈

struct OneOf<T: Equatable> {
  let a: [T]
  static func ∈(lhs: T, rhs: OneOf<T>) -> Bool {
    return rhs.a.contains(lhs)
  }
  init(_ a: T...) {
      self.a = a
  }
}

struct AllOf<T: Equatable> {
  let a: [T]
  static func ∈(lhs: T, rhs: AllOf<T>) -> Bool {
    return rhs.a.allSatisfy { $0 == lhs }
  }
  init(_ a: T...) {
      self.a = a
  }
}

let x = "hello" ∈ OneOf("hello", "world")
let y = "1" ∈ AllOf("1", "1")
8 Likes

I use contains for this. if ["yes", "y"].contains(x) { There are some languages that have an in keyword for this concept also. if x in ["yes", "y"] {

I wonder about the efficiency of building an array like that every time.

Changing the semantics of if statements to save a little typing doesn't seem like a great idea.

3 Likes

Ditto. I have avoided using contains in game loops so far for this exact reason. I would be interested to know if the performance impact was anything to worry about.

2 Likes

If the example is something like ["yes", "y"] you could have something like this which would avoid rebuilding the array every time:

class SomeDecider {
  static let confirmationAnswers = ["yes", "y"]

  func makeDecision(input: String) -> Bool {
    return SomeDecider.confirmationAnswers.contains(input)
  }
}
1 Like

I do kind of like the idea of this:

infix operator ==∈: Group
func ==∈<T: Equatable, C: Collection>(_ left: T, _ right: C) -> Bool where C.Element == T {
    right.first {$0 != left} == nil
}

print(1 ==∈ [1, 1]) // prints true

Unfortunately we have no keyboard key for "∈" and I haven't found a replacement that makes sense.

Also, FWIW if the Type is Hashable you could make both sides a Set

if Set(arrayLiteral: item) == Set(otherItems) { 
    //Do Your Thing 
} 

It's less efficient at run time, but it is nice and concise in code! :innocent:

1 Like

I asked a similar question (Hypothetical syntax for shortening repetitive logical statements) and got some interesting responses. It may be worth checking out.

1 Like

I use Array.contains(_:) for this most of the time. That probably prevents short-circuit evaluation, though. If the compiler was able to avoid building the entire array (could lazy do that?), it would be fine.

This is slightly trickier for less-than and greater-than. You can use a similar trick with a Range if the type in question is Strideable, but not everything is.

Of course. However, the compiler could do that for me perhaps by hoisting the array. It could also convert the contains into a series of if statements or use other optimizations if the array is hard coded like this. Don't know if any of that kind of optimization exists or is contemplated though.

Terms of Service

Privacy Policy

Cookie Policy