2
votes

I was working with Protocols in swift. I assumed it will be similar to an "interface" in other languages. I was testing how it will work with variables. Protocols are quite new to me as I have never seen interfaces with non-static variables. I created a Station protocol.

protocol Station{
    var id:String {get set}
    var name:String {get set} // station name
    var lines: Array<String> {get set} // all lines persent in this station 
}

Then Line which contains that station references. It also includes the Hashable protocol.

protocol Line: Hashable  {
    var lineId: String{get set}
    var station1:Station {get set}    // start station
    var station2:Station {get set}   // end station
    var stations:Array<Station> {get set}   // all the stations that is in this line   
}

Here's the implementation of that protocol

struct LinesImpl: Line{
    var station1: Station
    var station2: Station
    var stations: Array<Station>
    var lineId: String
    static func == (lhs: LinesImpl, rhs: LinesImpl) -> Bool {
        return (lhs.station1.name == rhs.station1.name && lhs.station2.name == rhs.station2.name && lhs.lineId == rhs.lineId)
    }
    func hash(into hasher: inout Hasher) {
        hasher.combine(station1.name)
        hasher.combine(station2.name)
        hasher.combine(lineId)
    }
    init(station1: Station, station2: Station, stations: [Station], lineId: String){
        self.station1 = station1
        self.station2 = station2
        self.stations = stations
        self.lineId = lineId   
    }
}

Once I added Hashable. I am no longer able to create references for Line protocol.

It says "Protocol 'Line' can only be used as a generic constraint because it has Self or associated type requirements" when I try to do this:

enter image description here

I see many questions like this over here but I still not able to see what is the error. Any help would be awesome. Thanks guys.

1

1 Answers

5
votes

When you added Hashable, that added Equatable, and that made this a Protocol with Associated Type (PAT) because Equatable is a PAT. A PAT is not a type; it's a tool for adding methods to other types.

You can't use a PAT as the type of a variable, you can't put it into an array, you can't pass it as a parameter directly, you can't return it as a value. The only point of a PAT is as a generic constraint (where L: Line). A PAT says what another concrete type must provide in order to be useable in some context.

How you should address this isn't clear. This doesn't really look like it should be a protocol at all. It depends on what code-reuse problem you're trying to solve here.

Protocols are generally about what something can do. Line seems like it's just trying to hide the implementation, without expressing anything; that's not a protocol. As written, there's no reason for generics or protocols here at all. What do the other implementations of Line look like? (I'm having trouble imagining how else you would possibly implement this type.)

I suspect the correct answer is to replace all of this with a Station struct and a Line struct. I'm not seeing where a protocol is pulling any weight.

Here's a way that I might implement what you're building, along with some new protocols to see what they're for. Some of this might be more than you'd need for this problem, but I wanted to show protocols in action.

// A protocol for "ID" types that automatically gives them handy inits
// Nested ID types mean that Line.ID can't get confused with Station.ID.
// The point of a protocol is to add additional features to a type like this.
protocol IDType: Hashable, ExpressibleByStringLiteral, CustomStringConvertible {
    var value: String { get }
    init(value: String)
}

extension IDType {
    // For convenience
    init(_ value: String) { self.init(value: value) }
    // Default impl for ExpressibleByStringLiteral
    init(stringLiteral value: String) { self.init(value: value) }
    // Default impl for CustomStringConvertible
    var description: String { return value }
}

struct Line: Equatable  {
    struct ID: IDType { let value: String }
    let id: ID
    let stations: [Station]
    var origin: Station { return stations.first! } // We ensure during init that stations is non-empty
    var terminus: Station { return stations.last! }

    init(id: ID, origin: Station, stops: [Station], terminus: Station) {
        self.id = id
        self.stations = [origin] + stops + [terminus]
    }
}

// Conforming Line to this protocol lets it print more beautifully.
extension Line: CustomStringConvertible {
    var description: String { return "\(id): \(origin) -> \(terminus)" }
}

// Stations can't contain Line directly. These are value types, and that would be
// recursive. But this is nice because it lets us construct immutable Stations
// and then glue them together with Lines which may even be in independent
// systems (for example, the bus system might be separate from the rail system,
// but share stations)
struct Station: Hashable {
    struct ID: IDType { let value: String }
    let id: ID
    let name: String

    func lines(in system: System) -> [Line] {
        return system.linesVisiting(station: self)
    }
}

extension Station: CustomStringConvertible {
    var description: String { return name }
}

struct System: Equatable {
    let lines: [Line]

    // Using Set here makes it clear there are no duplicates, and saves
    // some hassle turning it back into an Array, but we could also just
    // return Array here as Array(Set(...))
    var stations: Set<Station> {
        // Uniquify the stations
        return Set(lines.flatMap { $0.stations })
    }

    func linesVisiting(station: Station) -> [Line] {
        return lines.filter { $0.stations.contains(station) }
    }
}

// Some examples of using it.
let stationNames = ["Shady Grove", "Bethesda", "Metro Center", "Glenmont",
                    "Wiehle-Reston East", "Largo Town Center"]

// Build up a few stations; obviously there are many more
let stations = Dictionary(uniqueKeysWithValues:
    stationNames.map { ($0, Station(id: .init($0), name: $0)) })

// Define some lines
let redLine = Line(id: "OR",
                   origin: stations["Shady Grove"]!,
                   stops: [stations["Bethesda"]!, stations["Metro Center"]!],
                   terminus: stations["Glenmont"]!)

let silverLine = Line(id: "SV",
                      origin: stations["Wiehle-Reston East"]!,
                      stops: [stations["Metro Center"]!],
                      terminus: stations["Largo Town Center"]!)

// And glue them together into a system
let system = System(lines: [redLine, silverLine])