Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift protocol with associatedtype (ambiguous for type lookup)

I need to create generic function in protocol with default implementation in extension. It func should work with item as enum:RawRepresentable where RawValue == String always. I tried

protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable // how this add restriction to RawValue == String

    func test<T: SectionIdentifierEnum>(identifier: T) where T.RawValue == String
}

enum RequiresEnumDefault: String {
    case `default`
}

extension RequiresEnum where Self: UIViewController, SectionIdentifierEnum.RawValue == String {

    typealias SectionIdentifierEnum = RequiresEnumDefault

    func test<T: SectionIdentifierEnum>(identifier: T) where T.RawValue == String {
        print(T.rawValue)
    }

}

but i have errors

  • 'SectionIdentifierEnum' is ambiguous for type lookup in this context
  • 'RawValue' is not a member type of 'T'

Any solutions

like image 795
EvGeniy Ilyin Avatar asked Jul 27 '17 16:07

EvGeniy Ilyin


People also ask

What is an associated type in Swift?

The associated type is defined using the associatedtype keyword and tells the protocol that the subscript return type equals the append item type. This way we allow the protocol to be used with any associated type later defined. An example implementation could look as follows: What are the benefits of using associated types?

What is an associated type?

An associated type can be seen as a replacement of a specific type within a protocol definition. In other words: it’s a placeholder name of a type to use until the protocol is adopted and the exact type is specified. This is best explained by a simple code example.

What is an opaque type in Swift?

Since Swift 5.1, we can use another approach to working with generics: opaque types .The some keyword lets you “hide” the concrete return type of a property or function. The concrete type that’s returned can be determined by the implementation itself, as opposed to the calling code. This is why opaque types are sometimes called reverse generics.

What is the use of the associated type in a protocol?

Associated types prevent this by putting in a placeholder item: The associated type is defined using the associatedtype keyword and tells the protocol that the subscript return type equals the append item type. This way we allow the protocol to be used with any associated type later defined.


1 Answers

Generally when covering generics in the context of protocols, the generic typeholder is seen as representable by an associatedtype of the protocol. In your example this would be SectionIdentifierEnum, which acts as a placeholder for a constrained type. SectionIdenfierEnum is not, however, a protocol by itself, so cannot use it as a type constraint in a generic method. You can, however, use it as the type itself in your test(...) method.


Swift 3.1

Now, currently (Swift 3.1), you can't add sophisticated type constrains to an associatedtype. You could can, however, supply a default implementation available only for the case the where Self derives from UIViewController and implements the RequiresEnum protocol by setting the SectionIdentifierEnum type to the concrete RequiresEnumDefault type. The latter will ascertain that the associated RawValue is String for this default implementation, as the RawValue of the concrete RequiresEnumDefault type is String.

E.g.:

// Swift 3.1
// ---------
// Types that implement this protocol mustn't necessarily use a
// `SectionIdentifierEnum` type where `SectionIdentifierEnum.RawValue` 
// is constrained to equal `String`. 
protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable

    func test(identifier: SectionIdentifierEnum)
}

enum RequiresEnumDefault: String {
    case `default`
}

// This extension, however, is only available for types that use
// `RequiresEnumDefault ` as the concrete type of `SectionIdentifierEnum`
// (in which case `SectionIdentifierEnum.RawValue` is `String`).
extension RequiresEnum where Self: UIViewController, SectionIdentifierEnum == RequiresEnumDefault {

    func test(identifier: SectionIdentifierEnum) {
        print(identifier.rawValue)
    }
}

// Example usage.
class MyViewController : UIViewController, RequiresEnum {
    typealias SectionIdentifierEnum = RequiresEnumDefault
    // ...
}

let foo = MyViewController()
foo.test(identifier: RequiresEnumDefault.default) 
  // prints "default" (using extension:s default implementation)

Above, the default implementation of test(...) is only available when SectionIdentifierEnum equals the concrete type RequireEnumDefault (and Self derives from UIViewController ...). If instead you want it to only be available when SectionIdentifierEnum is any enum with String typed RawValue, you modify the type constraint of the extensions accordingly:

protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable

    func test(identifier: SectionIdentifierEnum)
}

enum RequiresEnumDefault: String {
    case `default`
}

extension RequiresEnum where Self: UIViewController, SectionIdentifierEnum.RawValue == String {

    func test(identifier: SectionIdentifierEnum) {
        print(identifier.rawValue)
    }
}


// Example usage.
enum EnumWithStringRawValue: String {
    case foo
}

class MyViewController : UIViewController, RequiresEnum {
    typealias SectionIdentifierEnum = EnumWithStringRawValue
    // ...
}

let foo = MyViewController()
foo.test(identifier: EnumWithStringRawValue.foo)
    // prints "foo" (using extension:s default implementation)

Once Swift 4 is released, you'll be able to add more sophisticated constraints to associatedtype:s, as per the implementation of Swift evolution proposal:

  • SE-0142: Permit where clauses to constrain associated types

In which case the above can be modified into:

// Swift 4
// -------
// Here, all types that implement this protocol must use a
// `SectionIdentifierEnum` type where `SectionIdentifierEnum.RawValue` 
// is equal to `String`. 
protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable 
        where SectionIdentifierEnum.RawValue == String

    func test(identifier: SectionIdentifierEnum)
}

enum RequiresEnumDefault: String {
    case `default`
}

// For the specific case where `SectionIdentifierEnum` equals
// `RequiresEnumDefault` (and where `Self` derives from `UIViewController`), 
// this default implementation is readily available.
extension RequiresEnum where Self: UIViewController, SectionIdentifierEnum == RequiresEnumDefault {

    func test(identifier: SectionIdentifierEnum) {
        print(identifier.rawValue)
    }

}

// Example usage.
class MyViewController : UIViewController, RequiresEnum {
    typealias SectionIdentifierEnum = RequiresEnumDefault
    // ...
}

let foo = MyViewController()
foo.test(identifier: RequiresEnumDefault.default) 
  // prints "default" (using extension:s default implementation)

Likewise modifying the constraint on the default implementation of test(...) not only to the case where SectionIdentifierEnum equals RequiresEnumDefault (but to any enum: in this case we know such enum will always have a RawValue String, due to the constraint on the associatedtype in the protocol definition).

protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable 
        where SectionIdentifierEnum.RawValue == String

    func test(identifier: SectionIdentifierEnum)
}

enum RequiresEnumDefault: String {
    case `default`
}

// From the constraint on the `RawValue` of `SectionIdentifierEnum`
// above, we know that `RawValue` equals `String`.
extension RequiresEnum where Self: UIViewController {

    func test(identifier: SectionIdentifierEnum) {
        print(identifier.rawValue)
    }
}

// Example usage.
enum EnumWithStringRawValue: String {
    case foo
}
class MyViewController : UIViewController, RequiresEnum {
    typealias SectionIdentifierEnum = EnumWithStringRawValue
    // ...
}

let foo = MyViewController()
foo.test(identifier: EnumWithStringRawValue.foo) 
  // prints "foo" (using extension:s default implementation)
like image 148
dfrib Avatar answered Sep 23 '22 05:09

dfrib