Home > front end >  enum with same type as protocol method
enum with same type as protocol method

Time:09-17

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 type MSP.Next and MSP

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)
  • Related