Brent’s Feed Protocol Problem

Brent has a problem:

//platform.twitter.com/widgets.js
His problem – protocols are seemingly messing up with mojo. It happens, and Equatable with its Self requirement can be quite annoying at times. However, his problem, at least as I understand it, is easily solvable, albeit with a bunch of code.

The key things to note about Brent's problem is that the Folder and the Feed are tightly coupled together; they have a strong type relationship together. So, we can use generics to help with this:

protocol Feed {
    var url: String {get}
}

protocol Folder {
    typealias FeedType

    var feeds: [FeedType] {get}
    func addFeeds(feedsToAdd: [FeedType])
}

class LocalFeed: Feed, Equatable {
    var url: String
    init(url: String) {
        self.url = url
    }
}

func ==(lhs: LocalFeed, rhs: LocalFeed) -> Bool {
    return lhs.url == rhs.url
}

class LocalFolder: Folder {
    var feeds = [LocalFeed]()

    func addFeeds(feedsToAdd: [LocalFeed]) {
        for oneFeed in feedsToAdd {
            if !feeds.contains(oneFeed) {
                feeds += [oneFeed]
            }
        }
    }
}

Basically, move Equatable to the type LocalFeed and every other feed type, and create a typealias on the Folder protocol so a specific type of Feed can be used.

We can go one step further, but we will see a slight problem when we do:

protocol Feed {
    var url: String {get}
}

protocol Folder {
    typealias FeedType : Equatable

    var feeds: [FeedType] { get set }
    mutating func addFeeds(feedsToAdd: [FeedType])
}

extension Folder {
    mutating func addFeeds(feedsToAdd: [FeedType]) {
        for oneFeed in feedsToAdd {
            if !feeds.contains(oneFeed) {
                feeds.append(oneFeed)
            }
        }
    }
}

class LocalFeed: Feed, Equatable {
    var url: String
    init(url: String) {
        self.url = url
    }
}

func ==(lhs: LocalFeed, rhs: LocalFeed) -> Bool {
    return lhs.url == rhs.url
}

class LocalFolder: Folder {
    var feeds = [LocalFeed]()
}

Now all Folder implementations get the addFeeds functionality for free. BUT, we have to clearly mark what could be potentially mutating. The big side-effect of this is that feeds becomes mutable outside of addFeeds, which is not good.

The way to fix this, or the only way I know how to fix this, is to create a protocol, that by convention, people agree not to use. Something like this:

protocol Feed {
    var url: String {get}
}

protocol Folder : _Folder {
    typealias FeedType : Equatable

    var feeds: [FeedType] { get }
    mutating func addFeeds(feedsToAdd: [FeedType])
}

protocol _Folder {
    typealias FeedType : Equatable
    var _feedStore: [FeedType] { get set }
}

extension Folder {
    mutating func addFeeds(feedsToAdd: [FeedType]) {
        for oneFeed in feedsToAdd {
            if !feeds.contains(oneFeed) {
                _feedStore.append(oneFeed)
            }
        }
    }
}

class LocalFeed : Feed, Equatable {
    var url: String
    init(url: String) {
        self.url = url
    }
}

func ==<T : Feed>(lhs: T, rhs: T) -> Bool {
    return lhs.url == rhs.url
}

class LocalFolder : Folder {
    var _feedStore : [LocalFeed] = []
    var feeds: [LocalFeed] { return _feedStore }
}

The _Folder protocol hides the mutation exposure of _feedStore. Oh, and while we're at it, lets change == to be generic and work for any type of Feed by default by comparing their respective url members.

Anyhow, hope that helps somewhat.

Brent’s Feed Protocol Problem