2
votes

I have a generic protocol:

protocol SectionType {
    associatedtype I: Equatable
    associatedtype T: Equatable
    var info: I? { get }
    var items: [T] { get }
}

and an Array extension for it:

/// Offers additional method(s) to process SectionType list.
extension Array where Element: SectionType {
    /// Dummy comparision method. Implementation is not that relevant but just to employ Equatable.
    /// - Parameter to: Another array of sections.
    /// - Returns: True if first info and first elemements in both arrays exist and both are equal.
    func dummyCompare(with otherArray: [Element]) -> Bool {
        guard
            let first = self.first,
            let firstOther = otherArray.first,
            let firstElement = first.items.first,
            let firstOtherElement = firstOther.items.first,
            let firstInfo = first.info, let firstOtherInfo = firstOther.info
            else { return false }
        return firstInfo == firstOtherInfo && firstElement == firstOtherElement
    }
}

No problem when using with concrete implementations:

Example 1: Works with specific implementation with build-in String type:

Declaration:

struct StringSection: SectionType {
    let info: String?
    let items: [String]
}

Usage:

let stringSection1 = StringSection(info: "Info 1", items: ["Item 1", "Item 2"])
let stringSection2 = StringSection(info: "Info 2", items: ["Item 3", "Item 4"])
let stringSections1 = [stringSection1, stringSection2]
let stringSections2 = [stringSection2, stringSection1]
var areEqual = stringSections1.dummyCompare(with: stringSections2)
print("String section 1 equals 2?: \(areEqual)")

Example 2 - Works with specific implementation with custom types:

Declarations:

protocol SectionInfoType {
    var title: String { get }
}

/// BTW: This is just Swift's stragne way of implementing Equatable protocol for your type:
func == (lhs: SectionInfoType, rhs: SectionInfoType) -> Bool {
    return lhs.title == rhs.title
}

struct SpecificSectionInfo: SectionInfoType, Equatable {
    let title: String
    static func == (lhs: SpecificSectionInfo, rhs: SpecificSectionInfo) -> Bool {
        return lhs.title == rhs.title
    }
}

protocol SectionItemType {
    var text: String { get }
}

/// BTW: This is just Swift's stragne way of implementing Equatable protocol for your type:
func == (lhs: SectionItemType, rhs: SectionItemType) -> Bool {
    return lhs.text == rhs.text
}

struct SpecificSectionItem: SectionItemType, Equatable {
    let text: String
    static func == (lhs: SpecificSectionItem, rhs: SpecificSectionItem) -> Bool {
        return lhs.text == rhs.text
    }
}

struct SpecificSection: SectionType {
    let info: SpecificSectionInfo?
    let items: [SpecificSectionItem]
}

Usage:

let specInfo1 = SpecificSectionInfo(title: "Info 1")
let specItem1 = SpecificSectionItem(text: "Specific item 1")
let specItem2 = SpecificSectionItem(text: "Specific item 2")
let specInfo2 = SpecificSectionInfo(title: "Info 2")
let specItem3 = SpecificSectionItem(text: "Specific item 3")
let specItem4 = SpecificSectionItem(text: "Specific item 4")
let specSection1 = SpecificSection(info: specInfo1, items: [specItem1, specItem2])
let specSection2 = SpecificSection(info: specInfo2, items: [specItem3, specItem4])
let specSections1 = [specSection1, specSection2]
let specSections2 = [specSection2, specSection1]
let areEqual = specSections1.dummyCompare(with: specSections2)
print("Specific section 1 equals 2?: \(areEqual)")

So far, so good, everything works and compiles. But ... I have at least 2 problems with this approach:

Problem 1:

Just from 2 examples above, one can see that this approach needs 'specific' implementation of a SectionType protocol for every combination of info and items type. This seems not so efficient (tremendous amount of code for every each implementation) nor generic.

What I need is a more generalised embodiment of SectionType protocol where types for info and items need to be protocols (provided from external APIs as protocols).

A perfect example (but does not compile):

struct Section: SectionType {
    typealias I = SectionInfoType
    typealias T = SectionItemType
    let info: I?
    let items: [T]
}

Problem 2:

I need to be able to pass it over to other protocol oriented API, f.ex.: as an argument to function like:

func consume(section: SectionType<SectionInfoType, SectionItemType>) {}

But above function declaration produces: Cannot specialize non-generic type 'SectionType' with Xcode proposing a fix: 'Delete <SectionInfoType, SectionItemType>' resulting in this:

func consume(section: SectionType) {}

This does not compile neither and I can not find a way to make it work.

Is it possible to do or is that Swift 3 limitation (I am using Swift 3.1)?

I created Git repository demonstrating those issues. Feel free to collaborate:

https://github.com/lukaszmargielewski/swift3-learning-generics

1
Why not make Section generic such as in your previous question? e.g struct Section<InfoT: Equatable, ItemsT: Equatable> : SectionType {...}.Hamish
@Hamish - this actually never fully worked when implementation of a method in array extension actually tried to use == (Equatable overload function). Note the example from there does not actually use it. When added -it gave compilation error: SectionInfoType does not conform to the Equatable protocol even if overload worked outside Array extension. That question also did not explore possibilities of specifying inferred types as protocols.Lukasz
Your Array extension should work fine so long as you're talking in terms of Element.I and Element.T. Sounds like you're trying to treat a protocol type as a concrete type that conforms to a protocol (this isn't possible, protocols don't conform to themselves – solution is usually a type eraser). It would seem to me that would be the issue worth pursuing, rather than manually specialising Section. (re: downvote, not me!).Hamish
Both of those are achievable with a generic container rather than a protocol. But agreed, that wasn't an answer (which is why I put it in a comment; I wish I had a real answer, but I don't). It may be possible to beat this into submission and create something that compiles. It is not possible to do it in a clean and elegant way in Swift 3. Swift 4 will be a bit better.Rob Napier
For one discussion on how generic structs can be just as flexible as protocols for implementation independence, and often are a better solution in Swift (and definitely Swift 3), see thedotpost.com/2016/01/…Rob Napier

1 Answers

0
votes

If I understand correctly problem 2 you want your a function that consumes a generic variable that is also specialised, adhering to protocol SectionType. Try:

func consume<Section: SectionType>(section: Section) {
    let foo = section.info
    let foobar = section.items
}