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.Next
andMSP
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)