I would like to introduce a protocol which would force my Enums to have variable next returning enum of the same type. I then would like to be able to call method to compare two enums.
protocol MySequenceProtocol {
associatedtype T
var next: T { get }
}
func compareNext<T: MySequenceProtocol>(input: T, target: T) {
if input.next == target {
print("match")
}
}
compareNext(input: Weekdays.monday.next, target: Weekdays.tuesday)
compareNext(input: Months.jan.next, target: Months.feb)
However, I'm having an error Binary operator '==' cannot be applied to operands of type 'T.T' and 'T'
My full code with few enum examples:
protocol MySequenceProtocol {
associatedtype T
var next: T { get }
}
enum Weekdays: MySequenceProtocol {
case monday, tuesday, wednesday, thursday, friday, saturday, sunday
var next: Weekdays {
switch self {
case .monday:
return .tuesday
default:
return .monday
}
}
}
enum Months: MySequenceProtocol {
case jan, feb, march
var next: Months {
switch self {
case .jan:
return .feb
case .feb:
return .march
default:
return .jan
}
}
}
func compareNext<T: MySequenceProtocol>(input: T, target: T) {
if input.next == target { /// error here
print("match")
}
}
I feel like the problem is in constraining protocol to return the same T, however I'm not sure how to express it in Swift.
CodePudding user response:
Your protocol declaration doesn't actually express your requirements correctly. Using an associatedType requirement for next makes it possible to conform with types whose next is of a different type than the conforming type itself.
What you actually need is Self, which refers to the conforming type.
protocol MySequenceProtocol {
var next: Self { get }
}
Then you also need to specify that your generic type constraint T conforms to Equatable and make your that your enums actually conform to Equatable.
func compareNext<T: MySequenceProtocol>(input: T, target: T) where T: Equatable {
if input.next == target {
print("match")
}
}
enum Weekdays: MySequenceProtocol, Equatable {
...
}
enum Months: MySequenceProtocol, Equatable {
...
}
You are also calling your compareNext function incorrectly. You should simply pass the enum case to it, you shouldn't call next, since the function itself already calls next.
compareNext(input: Weekdays.monday, target: Weekdays.tuesday)
compareNext(input: Months.jan, target: Months.feb)
CodePudding user response:
You can make the error message more useful by using names other than just T:
protocol MySequenceProtocol {
associatedtype Next
var next: Next { get }
}
func compareNext<MSP: MySequenceProtocol>(input: MSP, target: MSP) {
if input.next == target { /// error here
print("match")
}
}
What'll then get is:
error: binary operator
==cannot be applied to operands of typeMSP.NextandMSP
Which makes it clear: there's no guarentee that MSP and its Next associated type are the same type, thus, they can't be compared with ==.
The most salient fix is to just require Next to be the same as Self:
protocol MySequenceProtocol {
associatedtype Next where Next == Self
var next: Next { get }
}
Now of course, this is what a regular old Self requirement is for!
This now gives you a new error:
error: binary operator '==' cannot be applied to two 'MSP' operands
This makes sense, because you never expressed the requirement that MSP (or the type MySequenceProtocol to which it's constrained) are equatable. Fixing that:
func compareNext<MSP: MySequenceProtocol & Equatable>(input: MSP, target: MSP) {
if input.next == target { /// error here
print("match")
}
}
compareNext(input: Weekdays.monday.next, target: Weekdays.tuesday)
compareNext(input: Months.jan.next, target: Months.feb)
