Swift3.0 で Array の Element が特定の型のときだけ extension したい


Swift3.0

Swift3.0 では以下のように Array を直接拡張しようとすると error: same-type requirement makes generic parameter 'Element' non-generic と言われてできない。

extension Array where Element == Book {

...
}

調べてみたら以下の2つのやり方で実現できそうだった。


1. Collection または Sequence を拡張する

struct Book {

let title: String
let author: String
}

extension Collection where Iterator.Element == Book {

subscript(title title: String) -> Book? {
return filter{ $0.title == title }.first
}

subscript(author author: String) -> Book? {
return filter{ $0.author == author }.first
}

}

let books = [
Book(title: "the starry rift", author: "james tiptree, jr"),
Book(title: "hello summer, goodbye", author: "michael coney"),
]

// タイトルが一致する Book を取得
books[title: "the starry rift"]?.author // "james tiptree, jr"
// 著者が一致する Book を取得
books[author: "michael coney"]?.title // "hello summer, goodbye"

let notBooks = [1, 2, 3]
// Array<Book> ではないのでエラー
notBooks[title: "1"] // error: ambiguous reference to member 'subscript'

上記の例では filter くらいしか使ってないので Sequence を拡張しても正しく動くはず。

ただ、このやり方だと「Collection を実装していて Element が Book」の型が Array 以外に存在したときに意図しない挙動をしそうで怖い。なさそうだけど。


2. 対応する protocol を作って Element がその protocol に準拠しているときだけ拡張する

protocol BookType {

var title: String { get }
var author: String { get }
}

struct Book: BookType {
let title: String
let author: String
}

extension Array where Element: BookType {

subscript(title title: String) -> Book? {
return filter{ $0.title == title }.first as? Book
}

subscript(author author: String) -> Book? {
return filter{ $0.author == author }.first as? Book
}

}

let books = [
Book(title: "the starry rift", author: "james tiptree, jr"),
Book(title: "hello summer, goodbye", author: "michael coney"),
]

// タイトルが一致する Book を取得
books[title: "the starry rift"]?.author // "james tiptree, jr"
// 著者が一致する Book を取得
books[author: "michael coney"]?.title // "hello summer, goodbye"

let notBooks = [1, 2, 3]
// Array<Book> ではないのでエラー
notBooks[title: "1"] // error: cannot subscript a value of type '[Int]'

KickStarter-Prelude のコードを読んでいるときに見つけた。

https://github.com/kickstarter/Kickstarter-Prelude/blob/master/Prelude/Array.swift

Array 自体を拡張できるのはよいけど、このためだけに protocol を定義するのはちょっとやり過ぎ感があるなぁ...


Swift3.1

Swift3.1 では最初に書いたような書き方ができるっぽい。やりたいことをストレートに表現できてよさそう。はやくこれになりたい。

extension Array where Element == Book {

...
}

https://github.com/apple/swift/blob/master/CHANGELOG.md#swift-31