web-dev-qa-db-ja.com

Swiftプロトコルで同等

これができるとは思わないが、とにかく尋ねる。私はプロトコルを持っています:

protocol X {}

そしてクラス:

class Y:X {}

私のコードの残りの部分では、プロトコルXを使用してすべてを参照します。そのコードでは、次のようなことができるようになります。

let a:X = ...
let b:X = ...
if a == b {...}

問題は、Equatableを実装しようとすると:

protocol X: Equatable {}
func ==(lhs:X, hrs:X) -> Bool {
    if let l = lhs as? Y, let r = hrs as? Y {
        return l.something == r.something
    }
    return false
} 

プロトコルの背後に実装を隠しながら、==の使用を試行および許可するというアイデア。

EquatableにはSelf参照があり、これを型として使用することはできなくなるため、Swiftはこれを好みません。一般的な引数としてのみ。

プロトコルが型として使用できなくなることなく、プロトコルに演算子を適用する方法を誰かが見つけましたか?

34
drekka

プロトコルにEquatableを直接実装すると、タイプとして使用できなくなり、プロトコルを使用する目的に反します。 Equatableに準拠せずにプロトコルに==関数を実装しただけでも、結果が誤っている可能性があります。これらの問題のデモについては、私のブログのこの投稿を参照してください。

https://khawerkhaliq.com/blog/Swift-protocols-equatable-part-one/

私が最もうまくいくことがわかったアプローチは、型消去を使用することです。これにより、プロトコルタイプ(タイプイレーザーでラップ)の==比較を行うことができます。プロトコルレベルでの作業を続けながら、実際の==比較は、正しい結果を確実にするために、基礎となる具体的な型に委任されることに注意することが重要です。

簡単な例を使用してタイプイレーザーを作成し、最後にテストコードを追加しました。タイプStringの定数をプロトコルに追加し、さまざまなシナリオをテストできるように、2つの適合タイプ(構造はデモ目的で最も簡単です)を作成しました。

使用されるタイプ消去方法の詳細な説明については、上記のブログ投稿のパート2をご覧ください。

https://khawerkhaliq.com/blog/Swift-protocols-equatable-part-two/

以下のコードは、実装する等価比較をサポートする必要があります。プロトコルタイプをタイプイレーサーインスタンスでラップするだけです。

protocol X {
    var name: String { get }
    func isEqualTo(_ other: X) -> Bool
    func asEquatable() -> AnyEquatableX
}

extension X where Self: Equatable {
    func isEqualTo(_ other: X) -> Bool {
        guard let otherX = other as? Self else { return false }
        return self == otherX
    }
    func asEquatable() -> AnyEquatableX {
        return AnyEquatableX(self)
    }
}

struct Y: X, Equatable {
    let name: String
    static func ==(lhs: Y, rhs: Y) -> Bool {
        return lhs.name == rhs.name
    }
}

struct Z: X, Equatable {
    let name: String
    static func ==(lhs: Z, rhs: Z) -> Bool {
        return lhs.name == rhs.name
    }
}

struct AnyEquatableX: X, Equatable {
    var name: String { return value.name }
    init(_ value: X) { self.value = value }
    private let value: X
    static func ==(lhs: AnyEquatableX, rhs: AnyEquatableX) -> Bool {
        return lhs.value.isEqualTo(rhs.value)
    }
}

// instances typed as the protocol
let y: X = Y(name: "My name")
let z: X = Z(name: "My name")
let equalY: X = Y(name: "My name")
let unequalY: X = Y(name: "Your name")

// equality tests
print(y.asEquatable() == z.asEquatable())           // prints false
print(y.asEquatable() == equalY.asEquatable())      // prints true
print(y.asEquatable() == unequalY.asEquatable())    // prints false

タイプ消しゴムはプロトコルに準拠しているため、プロトコルタイプのインスタンスが予想される場所であればどこでもタイプ消しゴムのインスタンスを使用できます。

お役に立てれば。

19
Khawer Khaliq

プロトコルをEquatableに準拠させることについて考え直す必要があるのは、多くの場合、意味をなさないためです。この例を考えてみましょう:

protocol Pet: Equatable {
  var age: Int { get }
}

extension Pet {
  static func == (lhs: Pet, rhs: Pet) -> Bool {
    return lhs.age == rhs.age
  }
}

struct Dog: Pet {
  let age: Int
  let favoriteFood: String
}

struct Cat: Pet {
  let age: Int
  let favoriteLitter: String
}

let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")

if rover == simba {
  print("Should this be true??")
}

==の実装内で型チェックを行うことをほのめかしますが、問題はPetsである以外の型のいずれかに関する情報がなく、すべてのことを知らないことですPet(後でBirdRabbitを追加する可能性があります)。これが本当に必要な場合、別のアプローチとして、C#のような言語が平等を実装する方法をモデリングすることができます。

protocol IsEqual {
  func isEqualTo(_ object: Any) -> Bool
}

protocol Pet: IsEqual {
  var age: Int { get }
}

struct Dog: Pet {
  let age: Int
  let favoriteFood: String

  func isEqualTo(_ object: Any) -> Bool {
    guard let otherDog = object as? Dog else { return false }

    return age == otherDog.age && favoriteFood == otherDog.favoriteFood
  }
}

struct Cat: Pet {
  let age: Int
  let favoriteLitter: String

  func isEqualTo(_ object: Any) -> Bool {
    guard let otherCat = object as? Cat else { return false }

    return age == otherCat.age && favoriteLitter == otherCat.favoriteLitter
  }
}

let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")

if !rover.isEqualTo(simba) {
  print("That's more like it.")
}

その時点で、本当に必要な場合は、Equatableを実装せずに==を実装できます。

static func == (lhs: IsEqual, rhs: IsEqual) -> Bool { return lhs.isEqualTo(rhs) }

ただし、この場合に注意しなければならないことの1つは、継承です。継承型をダウンキャストし、isEqualToが論理的に意味をなさない情報を消去できるためです。

しかし、最善の方法は、クラス/構造自体にのみ等価性を実装し、型チェックに別のメカニズムを使用することです。

8
Scott H

多分これはあなたに役立つでしょう:

protocol X:Equatable {
    var name: String {get set}

}

extension X {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.name == rhs.name
    }
}

struct Test : X {
    var name: String
}

let first = Test(name: "Test1")
let second = Test(name: "Test2")

print(first == second) // false

プロトコルのすべてのインスタンスがEquatableに準拠する必要がある理由は定かではありませんが、クラスにそれらの等価メソッドを実装させることを好みます。

この場合、プロトコルはシンプルのままにします。

protocol MyProtocol {
    func doSomething()
}

MyProtocolに準拠するオブジェクトがEquatableでもある必要がある場合は、MyProtocol & Equatable型制約として:

// Equivalent: func doSomething<T>(element1: T, element2: T) where T: MyProtocol & Equatable {
func doSomething<T: MyProtocol & Equatable>(element1: T, element2: T) {
    if element1 == element2 {
        element1.doSomething()
    }
}

これにより、仕様を明確に保ち、​​必要な場合にのみサブクラスに等値メソッドを実装させることができます。

6
redent84

==ポリモーフィズムを使用。それはちょっとしたコード臭です。フレームワークのユーザーに同等性をテストできるものを提供する場合は、structではなくprotocolを販売する必要があります。ただし、protocolsを販売しているstructsにはなれないというわけではありません。

struct Info: Equatable {
  let a: Int
  let b: String

  static func == (lhs: Info, rhs: Info) -> Bool {
    return lhs.a == rhs.a && lhs.b == rhs.b
  }
}

protocol HasInfo {
  var info: Info { get }
}

class FirstClass: HasInfo {
  /* ... */
}

class SecondClass: HasInfo {
  /* ... */
}

let x: HasInfo = FirstClass( /* ... */ )
let y: HasInfo = SecondClass( /* ... */ )

print(x == y) // nope
print(x.info == y.info) // yep

これは基本的に「これらのものがあり、同じものかどうかはわかりませんが、同じプロパティセットがあることはわかっているので、それらのプロパティが同じ。"これは、そのMoneyの例をどのように実装するかに非常に近いです。

3
Scott H

protocol extensionconstrainedをクラスタイプに実装する必要があります。その拡張の中に、Equatable演算子を実装する必要があります。

public protocol Protocolable: class, Equatable
{
    // Other stuff here...
}

public extension Protocolable where Self: TheClass
{
    public static func ==(lhs: Self, rhs:Self) -> Bool 
    {
        return lhs.name == rhs.name
    } 
}


public class TheClass: Protocolable
{
    public var name: String

    public init(named name: String)
    {
        self.name = name
    }
}

let aClass: TheClass = TheClass(named: "Cars")
let otherClass: TheClass = TheClass(named: "Wall-E")

if aClass == otherClass
{
    print("Equals")
}
else
{
    print("Non Equals")
}

ただし、クラスに演算子の実装を追加することをお勧めします。複雑にしないでおく ;-)

1
Adolfo

プロトコルにEquatableを実装できないと言う人はみな、十分な努力をしません。プロトコルXの例のソリューション(Swift 4.1)は次のとおりです。

_protocol X: Equatable {
    var something: Int { get }
}

// Define this operator in the global scope!
func ==<L: X, R: X>(l: L, r: R) -> Bool {
    return l.something == r.something
}
_

そしてそれは動作します!

_class Y: X {
    var something: Int = 14
}

struct Z: X {
    let something: Int = 9
}

let y = Y()
let z = Z()
print(y == z) // false

y.something = z.something
pirnt(y == z) // true
_

唯一の問題は、「プロトコルは汎用制約としてのみ使用できる」エラーのためにlet a: X = Y()を記述できないことです。

1
kelin