Created
August 5, 2015 20:15
-
-
Save ttepasse/d3399daa9932b06b5ad2 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
// Playing in the Playground with Brent Simmons problem: | |
// http://inessential.com/2015/08/05/swift_diary_9_where_im_stuck | |
protocol AccountLike : Equatable, Hashable { | |
var accountID: String { get } | |
} | |
// First let's make AccountLike Equatable. The Equatable protocol | |
// defines func ==(lhs: Self, rhs: Self) -> Bool | |
// Meaning you can only compare Types to themselfes, that is the Self requirement. | |
// So let's define the func on a yet to be defined generic Type called SomeType which | |
// has the generic constraint, that it has to conform to the AccountLike protocol. | |
func ==<SomeType: AccountLike>(lhs: SomeType, rhs: SomeType) -> Bool { | |
return lhs.accountID == rhs.accountID | |
} | |
var counter = 0 | |
func crappyHashValueGenerator() -> Int { | |
return counter++ | |
} | |
// Some concrete Account types: | |
class TwitterAccount : AccountLike { | |
var accountID: String | |
let hashValue = crappyHashValueGenerator() | |
init(accountID: String) { | |
self.accountID = accountID | |
} | |
} | |
class BlogAccount : AccountLike { | |
var accountID: String | |
let hashValue = crappyHashValueGenerator() | |
init(accountID: String) { | |
self.accountID = accountID | |
} | |
} | |
// Let's test them | |
let bs1 = TwitterAccount(accountID: "@brentsimmons") | |
let bs2 = TwitterAccount(accountID: "@brentsimmons") | |
let tt = TwitterAccount(accountID: "@ttepasse") | |
print(bs1 == bs1) // -> true | |
print(bs1 == bs2) // -> true | |
print(bs1 == tt) // -> false | |
let inessential = BlogAccount(accountID: "http://inessential.com/") | |
// On the other hand comparing different types doesn't work even in static analysis. | |
// Which is to be expected. | |
// print( bs1 == inessential ) | |
// Warning: Binary operator '==' cannot be applied to operands of type 'TwitterAccount' and 'Blogaccount' | |
// And yes: ”Hashing“ | |
print(bs1.hashValue) // 0 | |
print(bs2.hashValue) // 1 | |
print(tt.hashValue) // 2 | |
// This still doesn't work: | |
// class AccountManager { | |
// var accounts: Set<AccountLike> = [] | |
// init() {} | |
// } | |
// For some reason protocols as type doesn't work great with generic collections, even if the Swift | |
// documentation says otherwithe. Maybe a concrete type? But one cannot use a concrete type like in | |
// set<TwitterAccount> because accounts is to be a heterogeneous collection, not a homogeneous one. | |
// There is a mechanism for reducing heterogeneous types on their commonality of course: | |
class AccountBaseClass: AccountLike { | |
var accountID: String | |
let hashValue = crappyHashValueGenerator() | |
init(accountID: String) { | |
self.accountID = accountID | |
} | |
} | |
// And this still works: | |
class NewTwitterAccount: AccountBaseClass {} | |
class NewBlogAccount: AccountBaseClass {} | |
let nbs1 = NewTwitterAccount(accountID: "@brentsimmons") | |
let nbs2 = NewTwitterAccount(accountID: "@brentsimmons") | |
let ntt = NewTwitterAccount(accountID: "@ttepasse") | |
print(nbs1 == nbs1) // -> true | |
print(nbs1 == nbs2) // -> true | |
print(nbs1 == ntt) // -> false | |
let ninessential = NewBlogAccount(accountID: "http://inessential.com/") | |
// This comparison between different types on the other hand doesn't warn anymore | |
// because AccountBaseClass conforms to the Self requirement of ==. | |
print( nbs1 == ninessential ) // -> false | |
// (Maybe the comparison algorithm should be reworked) | |
// Tada | |
class AccountManager { | |
var accounts : Set<AccountBaseClass> = [nbs1, nbs2, ninessential] // contains NewTwitterAccount and NewBlogAccount | |
func contains(account: AccountBaseClass) -> Bool { | |
return self.accounts.contains(account) | |
} | |
} | |
let am = AccountManager() | |
am.contains(nbs1) // -> true | |
am.contains(ntt) // -> false | |
// Is using class inheritance a major flaw in our new universe of protocol-oriented programming? | |
// I don't think so. I tend to think of classes (structs, enums) as an information, what a thing | |
// is. Protocols on the other hand are information what a thing does. Hashable as a information | |
// that a thing does the hashValue thing, CollectionType as an information what collection methods | |
// a thing does. So I wouldn't use a Account(Like)-Protocol but something like this | |
// | |
// class AccountBaseClass : Equatable, Hashable { … } | |
// | |
// ... because an Account(Like)-protocol is not an “is”. But that may be just my opinion, of | |
// course. And I may be very wrong, of course. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment