5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

any Equatable 同士を比較する

Posted at

Equatableに適合したインスタンス同士を比較したい場合が時々あります。以下のコードでいうとGenericItem同士を比較できると、いろいろ便利な場面がありますが、残念ながら == オペレータで単純に比較する事はできません。

protocol GenericItem: Hashable {
	associatedtype Element
	var value: Element { get }
}

struct StringItem: GenericItem {
	var value: String
}

struct IntItem: GenericItem {
	var value: Int
}

let item1: any GenericItem = StringItem(value: "hello")
let item2: any GenericItem = StringItem(value: "hello")
let item3: any GenericItem = StringItem(value: "world")
let item4: any GenericItem = IntItem(value: 24)

// Binary operator '==' cannot be applied to two 'any GenericItem' operands
item1 == item2 // error 
item1 == item2 // error
item2 == item3 // error
item3 == item4 // error

この例で言うなら、GenericItemisEqual(to:)の類いのメソッドを用意して比較するなんて方法もあるようです。

protocol GenericItem: Equatable {
	func isEqual(to other: any GenericItem) -> Bool
}
extension GenericItem {
	func isEqual(to other: any GenericItem) -> Bool {
		if let other = other as? Self {
			return self == other
		}
		return false
	}
}
func compare(_ item1: any GenericItem, _ item2: any GenericItem) -> Bool {
	// error: Binary operator '==' cannot be applied to two 'any GenericItem' operands
	// return item1 == item2

	// OK
	return item1.isEqual(to: item2)
}

が、今回もっと簡単な方法を見つけたので、紹介したいと思います。

func == (lhs: any Equatable, rhs: any Equatable) -> Bool {
	return ([lhs] as NSArray) == ([rhs] as NSArray)
}

上記の呪文を唱えれば、以下のように、any Equatable 同士が比較できるようになります。

let item1: any Equatable = StringItem(value: "hello")
let item2: any Equatable = StringItem(value: "hello")
let item3: any Equatable = StringItem(value: "world")
let item4: any Equatable = IntItem(value: 24)

item1 == item2 // true
item1 == item2 // true
item2 == item3 // false
item3 == item4 // false

この例では、StringItemIntItemNSObjectのサブクラスでもNSObjectProtocolに適合しているわけでもありませんが、NSArray同士を比較するときはメンバーのany Equatable同士をなぜかいい感じで比較してくれるようです。

内部の仕組みがわかれば、NSArrayとかでラップしなくても、良い方法で比較できるかもしれません。

さて、一部の人には、any Equatable同士を比較したいのに、なぜ冒頭のコードはHashableになっているのかと気になった人もいるかもしれません、不思議なことにHashableEquatableにすると、挙動が変わります。

any Equatable同士を比較したいのに、対象のインスタンスは Hashableに適合していないと挙動が変わってしまうという不思議さです。

protocol GenericItem: Hashable { // <-- Hashable
	associatedtype Element
	var value: Element { get }
}
//...snip...
([item1] as NSArray) == ([item2] as NSArray) // true
([item2] as NSArray) == ([item3] as NSArray) // false
protocol GenericItem: Equatable { // <-- Equatable
	associatedtype Element
	var value: Element { get }
}
//...snip...
([item1] as NSArray) == ([item2] as NSArray) // false <-- !?
([item2] as NSArray) == ([item3] as NSArray) // false

ひょっとして、まだ見知らぬ罠が潜んでいる可能性はありますが、一つの方法として紹介したいと思います。

$ swift --version
swift-driver version: 1.62.15 Apple Swift version 5.7.1 (swiftlang-5.7.1.135.3 clang-1400.0.29.51)
Target: arm64-apple-macosx13.0
5
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?