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])