Подтвердить что ты не робот

Как проверить равенство перечислений Swift со связанными значениями

Я хочу проверить равенство двух значений переполнения Swift. Например:

enum SimpleToken {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == t2)

Однако компилятор не скомпилирует выражение равенства:

error: could not find an overload for '==' that accepts the supplied arguments
    XCTAssert(t1 == t2)
    ^~~~~~~~~~~~~~~~~~~

Должен ли я определить свою собственную перегрузку оператора равенства? Я надеялся, что компилятор Swift будет обрабатывать его автоматически, подобно Scala и Ocaml do.

4b9b3361

Ответ 1

Как отмечали другие, Swift не синтезирует необходимые операторы равенства автоматически. Позвольте мне предложить более чистую (IMHO) реализацию:

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}

public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    switch (lhs, rhs) {
    case let (.Name(a),   .Name(b)),
         let (.Number(a), .Number(b)):
      return a == b
    default:
      return false
    }
}

Это далеко не идеальный - там много повторений - но по крайней мере вам не нужно делать вложенные ключи с if-утверждениями внутри.

Ответ 2

Реализация Equatable является излишним ИМХО. Представьте, что у вас сложное и большое перечисление со многими падежами и множеством разных параметров. Эти параметры также должны быть реализованы в Equatable. Кроме того, кто сказал, что вы сравниваете случаи перечисления на основе "все или ничего"? Как насчет того, если вы тестируете значение и задали только один конкретный параметр enum? Я настоятельно рекомендую простой подход, например:

if case .NotRecognized = error {
    // Success
} else {
    XCTFail("wrong error")
}

... или в случае оценки параметров:

if case .Unauthorized401(_, let response, _) = networkError {
    XCTAssertEqual(response.statusCode, 401)
} else {
    XCTFail("Unauthorized401 was expected")
}

Найти более подробное описание здесь: https://mdcdeveloper.wordpress.com/2016/12/16/unit-testing-swift-enums/

Ответ 3

Кажется, что для операторов перечисления не генерируется оператор равенства, а также для структур.

"Если вы создаете свой собственный класс или структуру для представления сложной модели данных, например, значение" равно "для этого класса или структуры не является тем, что Свифт может угадать для вас". [1]

Чтобы реализовать сравнение равенства, нужно написать что-то вроде:

@infix func ==(a:SimpleToken, b:SimpleToken) -> Bool {
    switch(a) {

    case let .Name(sa):
        switch(b) {
        case let .Name(sb): return sa == sb
        default: return false
        }

    case let .Number(na):
        switch(b) {
        case let .Number(nb): return na == nb
        default: return false
        }
    }
}

[1] См. "Операторы эквивалентности" в https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_43

Ответ 4

Вот еще один вариант. Он в основном такой же, как и другие, за исключением того, что он избегает вложенных операторов switch, используя синтаксис if case. Я думаю, что это делает его более читаемым (/терпимым) и имеет преимущество в том, чтобы вообще избежать случая по умолчанию.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
extension SimpleToken {
    func isEqual(st: SimpleToken)->Bool {
        switch self {
        case .Name(let v1): 
            if case .Name(let v2) = st where v1 == v2 { return true }
        case .Number(let i1): 
            if case .Number(let i2) = st where i1 == i2 { return true }
        }
        return false
    }
}

func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
    return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

Ответ 5

enum MyEnum {
    case None
    case Simple(text: String)
    case Advanced(x: Int, y: Int)
}

func ==(lhs: MyEnum, rhs: MyEnum) -> Bool {
    switch (lhs, rhs) {
    case (.None, .None):
        return true
    case let (.Simple(v0), .Simple(v1)):
        return v0 == v1
    case let (.Advanced(x0, y0), .Advanced(x1, y1)):
        return x0 == x1 && y0 == y1
    default:
        return false
    }
}

Ответ 6

Я использую это обходное решение в unit test code:

extension SimpleToken: Equatable {}
func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    return String(stringInterpolationSegment: lhs) == String(stringInterpolationSegment: rhs)
}

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

Ответ 7

Другой вариант - сравнить строковые представления случаев:

XCTAssert(String(t1) == String(t2))

Например:

let t1 = SimpleToken.Number(123) // the string representation is "Number(123)"
let t2 = SimpleToken.Number(123)
let t3 = SimpleToken.Name("bob") // the string representation is "Name(\"bob\")"

String(t1) == String(t2) //true
String(t1) == String(t3) //false

Ответ 8

Другой подход с использованием if case с запятыми, который работает в Swift 3:

enum {
  case kindOne(String)
  case kindTwo(NSManagedObjectID)
  case kindThree(Int)

  static func ==(lhs: MyEnumType, rhs: MyEnumType) -> Bool {
    if case .kindOne(let l) = lhs,
        case .kindOne(let r) = rhs {
        return l == r
    }
    if case .kindTwo(let l) = lhs,
        case .kindTwo(let r) = rhs {
        return l == r
    }
    if case .kindThree(let l) = lhs,
        case .kindThree(let r) = rhs {
        return l == r
    }
    return false
  }
}

Вот как я писал в своем проекте. Но я не могу вспомнить, где я получил эту идею. (Я googled только сейчас, но не видел такого использования.) Любые комментарии будут оценены.

Ответ 9

t1 и t2 не являются числами, они являются экземплярами SimpleTokens со значениями.

Вы можете сказать

var t1 = SimpleToken.Number(123)

Затем вы можете сказать

t1 = SimpleToken.Name("Smith") 

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

Чтобы получить значение из t1, используйте оператор switch:

switch t1 {
    case let .Number(numValue):
        println("Number: \(numValue)")
    case let .Name(strValue):
        println("Name: \(strValue)")
}

Ответ 10

"Преимущество" при сравнении с принятым ответом заключается в том, что в операторе switch "main" нет случая "по умолчанию", поэтому, если вы расширите свое перечисление на другие случаи, компилятор заставит вас обновить остальную часть кода.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
extension SimpleToken {
    func isEqual(st: SimpleToken)->Bool {
        switch self {
        case .Name(let v1):
            switch st {
            case .Name(let v2): return v1 == v2
            default: return false
            }
        case .Number(let i1):
            switch st {
            case .Number(let i2): return i1 == i2
            default: return false
            }
        }
    }
}


func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
    return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

Ответ 11

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

Конечно, вы можете сделать оператор switch, но иногда приятно просто проверить одно значение в одной строке. Вы можете сделать это так:

// NOTE: there only 1 equal ('=') sign! Not the 2 ('==') that you're used to for the equality operator
// 2nd NOTE: Your variable must come 2nd in the clause

if case .yourEnumCase(associatedValueIfNeeded) = yourEnumVariable {
  // success
}

Если вы хотите сравнить 2 условия в одном и том же предложении if, вам нужно использовать запятую вместо оператора &&:

if someOtherCondition, case .yourEnumCase = yourEnumVariable {
  // success
}

Ответ 12

Вы можете сравнить с помощью переключателя

enum SimpleToken {
    case Name(String)
    case Number(Int)
}

let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

switch(t1) {

case let .Number(a):
    switch(t2) {
        case let . Number(b):
            if a == b
            {
                println("Equal")
        }
        default:
            println("Not equal")
    }
default:
    println("No Match")
}