Created
August 5, 2015 18:07
-
-
Save brentsimmons/7ec7e3669ff7ad17e446 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: Playground - noun: a place where people can play | |
import Cocoa | |
protocol Account: Equatable { | |
var accountID: String {get} | |
} | |
// https://twitter.com/optshiftk/status/628985834801336320 | |
func ==<T: Account>(lhs: T, rhs: T) -> Bool { | |
return lhs.accountID == rhs.accountID | |
} | |
class FooAccount: Account { | |
let accountID = "foo" | |
} | |
class BarAccount: Account { | |
let accountID = "bar" | |
} | |
let foo = FooAccount() | |
let bar = BarAccount() | |
var accounts = [Account]() | |
// Error on line 29: protocol 'Account' can only be used as a generic constraint because it has Self or associated type requirements |
import Cocoa
protocol AnyEquatable {
func isEqual(other: Any) -> Bool
}
protocol Account: AnyEquatable {
var accountID: String {get}
}
extension SequenceType where Generator.Element : AnyEquatable {
func containsAny(element: Any) -> Bool {
for e in self {
if e.isEqual(element) {
return true
}
}
return false
}
}
func containsAccount(accounts: [Account], element: Account) -> Bool {
for e in accounts {
if e.isEqual(element) {
return true
}
}
return false
}
func containsT<T: AnyEquatable>(array: [T], element: T) -> Bool {
for e in array {
if e.isEqual(element) {
return true
}
}
return false
}
class FooAccount: Account {
let accountID = "foo"
func isEqual(other: Any) -> Bool {
guard let _ = other as? FooAccount else {
return false
}
return true // TODO
}
}
class BarAccount: Account {
let accountID = "bar"
func isEqual(other: Any) -> Bool {
guard let _ = other as? BarAccount else {
return false
}
return true // TODO
}
}
let foo = FooAccount()
let bar = BarAccount()
var accounts: [Account] = [foo, bar]
containsAccount(accounts, element: foo) // works
containsT(accounts, element: foo) // error: Cannot convert value of type '[Account]' to expected argument type '[_]'
accounts.containsAny(foo) // error: Using 'Account' as a concrete type conformint to protocol 'AnyEquatable' is not supported
protocol Account {
var accountID: String { get }
}
extension SequenceType where Self.Generator.Element == Account {
func contains(element: Self.Generator.Element) -> Bool {
for currentElement in self {
if currentElement.accountID == element.accountID {
return true
}
}
return false
}
}
class FooAccount: Account {
let accountID = "foo"
}
class BarAccount: Account {
let accountID = "bar"
}
let foo = FooAccount()
let bar = BarAccount()
var accounts = [Account]()
if !accounts.contains(foo) { // works
accounts.append(foo)
}
if !accounts.contains(bar) { // works
accounts.append(bar)
}
How about this?
import Cocoa
protocol Feed {
var url: String {get}
func isEqualTo(other: Feed) -> Bool
}
extension Feed where Self: Equatable {
func isEqualTo(other: Feed) -> Bool {
return url == other.url
}
}
func ==<T: Feed where T:Equatable> (lhs: T, rhs: T) -> Bool {
return lhs.isEqualTo(rhs)
}
protocol Folder {
var feeds: [Feed] {get}
func addFeeds(feedsToAdd: [Feed])
}
// This could be done if the Folder feeds had a { get set }
//extension Folder {
// func addFeeds(feedsToAdd: [Feed]) {
// for oneFeed in feedsToAdd {
// if !feeds.contains({ $0.isEqualTo (oneFeed) }) {
// feeds += [oneFeed]
// }
// }
// }
//}
class LocalFeed: Feed {
var url: String
init(url: String) {
self.url = url
}
}
extension LocalFeed: Equatable {}
class LocalFolder: Folder {
var feeds = [Feed]()
func addFeeds(feedsToAdd: [Feed]) {
for oneFeed in feedsToAdd {
if !feeds.contains({ $0.isEqualTo (oneFeed) }) {
feeds += [oneFeed]
}
}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The issue here is that Swift does not allow you to use a protocol as a type parameter when said protocol has a Self or associated type requirement.
[Account]
desugars toArray<Account>
, andAccount
conforms toEquatable
, whose definition contains a Self type:Currently in Swift there is no way use type bounds as part of a variable's type annotation. You can't say, for instance,
var accounts: Array<Any: Account>
, whereAny: Account
denotes "any type that conforms to theAccount
protocol". Normally,Any: Account
(which is only a theoretical construct in Swift here) and justAccount
mean exactly the same thing. These two behave the same way:But this sort of generic notion is not possible on the level of a variable's type annotation. In languages with similar functionality, this notion of variable-level type bounds is possible. For example, Scala has either
val accounts: Seq[_ <: Account]
orval accounts: Seq[T] forSome { type T <: Account }
and calls it an existential type with a type bound, whereas Java allowsList<? extends Account> accounts
and calls it bounded wildcards.Further adding to this mess is that Swift currently offers no control over variance. Suppose you have a type
B
that is a subtype ofA
. Then wherever you have alet x: A
you may assign to it a value of typeB
. But more interestingly[B]
is considered a subtype of[A]
andB?
a subtype ofA?
because Arrays and Optionals are magically covariant in Swift. If you were to define your own container type, like aclass Box<T>
, then there is no way for you to tell the compiler that you considerBox<B>
to be a valid subtype ofBox<A>
. C#, for example, lets you sayclass Box<out T>
to mark the type parameter as covariant.Objective-C does not suffer this problem on two different levels. For one, it supports
__covariant
and__contravariant
, so in terms of variance support it is ahead of Swift. In this particular instance,[- NSArray containsObject:]
is simply not concerned with whether an object is capable of proper equality checks. All derived classes ofNSObject
inherit an identity comparison implementation of[- NSObject isEqual:]
anyway, and it is up to the subclass's responsibility to override it with something meaningful, so other than disciplined vigilance, there's no compile-time guarantee that[- NSArray containsObject:]
will do the right thing.My current best guess as to why a protocol as a type parameter is not allowed under certain circumstances, is that variance can interact with associated / self type constraints in surprising manners. All in all, this inability to hold an simple array of things that are only common by protocol conformance is indicative that Swift's type system still has many refinements that can be made.