Skip to content

Instantly share code, notes, and snippets.

@ttepasse
Created August 5, 2015 20:15
Show Gist options
  • Save ttepasse/d3399daa9932b06b5ad2 to your computer and use it in GitHub Desktop.
Save ttepasse/d3399daa9932b06b5ad2 to your computer and use it in GitHub Desktop.
// 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