Как я могу сопоставить шаблон с оператором OR в перечислении со связанными значениями? - PullRequest
0 голосов
/ 12 января 2019

У меня есть это перечисление:

enum Foo {
    case a(x: Int)
    case b(x: Int)
    case c
    case d
}

и foo

let foo = Foo.a(x: 10)

Я хочу проверить, является ли foo или a, b или c, независимо от того, что x.

С оператором switch я мог бы сделать:

switch foo {
case .a, .b, .c:
    ...
case .d:
    break
}

но это немного затянуто.

Я думал, что смогу сделать то же самое с if case:

if case .a, .b, .c = foo { ... }

Это привело к ошибке компилятора.

Затем я нашел этот вопрос и попробовал:

if [Foo.a, Foo.b, Foo.c].contains(foo) { ... }

Компилятор считал, что массив имеет тип [Any], так что это тоже не работает ...

Что я могу сделать, кроме как извлечь его как метод и вызвать этот метод? В Swift 4.2 есть что-то новое, что решает эту проблему?

Ответы [ 3 ]

0 голосов
/ 12 января 2019

Если вы планируете повторять этот тест несколько раз в нескольких местах, то дублирование длинноукладной версии действительно будет раздражать; однако вы можете просто инкапсулировать этот бит кода в расширение.

extension Foo {
  var isABorC: Bool {
    switch self {
    case .a, .b, .c:
      return true
    default:
      return false
    }
  }
}

Итак, теперь ваш тест становится примерно таким:

if foo.isABorC { ... }

Или вы можете просто сделать это частью объявления enum:

enum Foo {
  case a(x: Int)
  case b(x: Int)
  case c
  case d

  var isABorC: Bool {
    switch self {
    case .a, .b, .c:
      return true
    case .d:
      return false
    }
  }
}

В качестве примера можно привести документацию swift (4.2) с использованием вложенного перечисления для реализации рангов для колоды карт, в которую можно добавить isFaceCard var.

Суть в том, что вам не нужно бесконечно дублировать этот бит текста ad nauseam . Вы можете скрыть это, пока не найдете более элегантное решение.

0 голосов
/ 02 марта 2019

Swift не поддерживает это, потому что Foo экземпляры нуждаются в сопоставлении с образцом, поскольку они не Equatable. И единственный разделитель, который допускает многократное сопоставление с шаблоном, это ,, и этот оператор соответствует операции and, у вас не может быть or.

Один уродливый (и я бы сказал, неправильный или вводящий в заблуждение) подход заключается в добавлении соответствия к Equatable и игнорировании связанных значений:

enum Foo: Equatable {
    case a(x: Int)
    case b(x: Int)
    case c
    case d

    static func ==(_ lhs: Foo, _ rhs: Foo) -> Bool {
        switch (lhs, rhs) {
        case (.a, .a): return true
        case (.b, .b): return true
        case (.c, .c): return true
        case (.d, .d): return true
        default: return false
        }
    }
}

Затем вы можете сделать что-то вроде этого:

if [Foo.a(x: 0), Foo.b(x: 0), Foo.c].contains(foo) { ... }

Другой подход - добавить свойство index и использовать его при тестировании:

enum Foo {
    case a(x: Int)
    case b(x: Int)
    case c
    case d

    var index: Int {
        switch self {
        case .a: return 0
        case .b: return 1
        case .c: return 2
        case .d: return 3
        }
    }
}

И используйте его в соответствии с

if [Foo.a(x: 0), Foo.b(x: 0), Foo.c].map({ $0.index }).contains(foo.index) { ... }

Оба решения более многословны, чем простой переключатель, и они будут выполнимы только в том случае, если вам нужно будет использовать их много раз.

В качестве альтернативы вы можете расширить Array примерно так:

extension Array where Element == Foo {
    func matchesCase(_ foo: Foo) -> Bool {
        return contains {
            switch ($0, foo) {
            case (.a, .a): return true
            case (.b, .b): return true
            case (.c, .c): return true
            case (.d, .d): return true
            default: return false
            }
        }
    }
}

, и используйте его так:

if [Foo.a(x: 0), Foo.b(x: 0), Foo.c].matchesCase(foo) { ... }

И четвертое решение :). Добавление функции sameCase:

enum Foo {
    case a(x: Int)
    case b(x: Int)
    case c
    case d

    func sameCase(_ foo: Foo) -> Bool {
        switch self {
        // a little bit more verbose, but don't risk missing new cases
        case .a: if case .a = foo { return true } else { return false }
        case .b: if case .b = foo { return true } else { return false }
        case .c: if case .c = foo { return true } else { return false }
        case .d: if case .d = foo { return true } else { return false }
        }
    }
}

Использование:

if [Foo.a(x: 0), Foo.b(x: 0), Foo.c].contains(where: foo.sameCase) { ... }
// or
if foo.sameCase(.a(x: 0)) || foo.sameCase(.b(x: 0)) || foo.sameCase(.c) { ... }
0 голосов
/ 12 января 2019

К сожалению, другого пути нет.

Это потому, что Foo.a имеет тип (Int) -> Foo. Причина, по которой вы не можете использовать array.contains, заключается в том, что замыкание и Foo являются разными типами, поэтому компилятор предполагает, что вам нужен массив Any.

Чтобы увидеть это явление самостоятельно, попробуйте этот код:

print(type(of: Foo.a))

Вы получите (Int) -> Foo.

...