Generic function inside generic type

I have a "generic" protocol :

public protocol AnyList
{
  subscript(itemName: String) -> AnyItem? { get }
  
  subscript<identifierT : SomeStringBasedType>(Identifier: identifierT) -> AnyItem? { get }
}

I implement this :

class ConcreteList<identifierT> : AnyList
{
  private var innerDictionary: [identifierT : AnyItem]
  
  // … string-based subscript

  subscript<identifierT : SomeStringBasedType>(identifier: identifierT) -> AnyItem?
  {
    return innerDictionary[identifier]
  }
}

But this gives me the error :

Cannot subscript a value of type '[identifierT : AnyItem]' with an index of type 'identifierT'

Obviously, the two identifierT types are regarded as different, even though they are, in this case, the same type.

So, I change the parameter type name and force cast the inner type to the outer type :

class ConcreteList<identifierT> : AnyList
{
  private var innerDictionary: [identifierT : AnyItem]

  subscript<innerIdentifierT : SomeStringBasedType>(identifier: innerIdentifierT) -> AnyItem?
  {
    return innerDictionary[identifier as! identifierT]
  }
}

This all seems a bit hacky™ and I was wondering if the great and the good had found any other way of equating the two types?

They're not the same type. Consider:

let list: AnyList = ConcreteList<Color>()
print(list[Weekday.monday])

This is a case where either your protocol should use an associated type rather than a generic requirement, or ConcreteList shouldn't conform to it.

The problem with this is that then I can't use the AnyList as a type eraser because of PAT restrictions, which is the reason why I wanted the generic method rather than an associated type

That's kind of what type erasure means, though: throwing away information about the type. Either you care about the list only being subscripted by the correct identifier type, or you don't.

So, effectively, the "hack" is the only real solution :kissing_heart:

It's not my preferred solution. If you don't want to write a manual type eraser, I'd rather do something like this:

public protocol AnyList
{
  subscript(itemName: String) -> AnyItem? { get }
}

extension AnyList
{
  // if you still want this
  subscript<identifierT : SomeStringBasedType>(assumingAppropriate Identifier: identifierT) -> AnyItem? {
    return self[Identifier.rawValue]
  }
}

That way you're acknowledging that you're not using the entry point specific to the conforming type, which still allowing an unlabeled subscript on the conforming type that does do the static check. I admit that the naive implementation here is probably slower, though, since it's going to have to go from raw value back to identifier and that's going to do a string comparison to see if it's valid.

Slight problem there - the dictionary holding the items is keyed by the enum behind the generic parameter type. All your solution does in my case is to call the string subscript, which then builds the enum value from its raw string value before accessing it in the dictionary by the enum type :wink:

That's true, which is why I said it was potentially less efficient. (I should amend that to "or less safe" if the init(rawValue:) that gets used doesn't do any validation.) The important thing is the labeled subscript, though: you've called out that this entry point is going to do stringy things, as opposed to the "good" entry point that has a concrete type.

OK. In that case, I'll just have to get "posh" and do things according to the book :sunglasses:

  public subscript<innerIdentifierT : StringEnumType>(identifier: innerIdentifierT) -> AnyItem?
  {
    guard let identifier = identifier as? identifierT else
    {
      return nil
    }
    
    return innerDictionary[identifier]
  }
1 Like
Terms of Service

Privacy Policy

Cookie Policy