Is it code smell when you must remember to use type(of: arg).foo, not T.foo?

I don't remember running into in other languages, probably because most languages don't have protocols with type (static) requirements. For example, say I have some classes that I'm saving to a database. I define a protocol:

protocol PersistentRecord {
    static var tableName: String { get }
    var id: UUID { get }
    func toSql(builder: SqlUpsertBuilder)
}

And a function to save my objects:

func insertNewRecord<R: PersistentRecord>(_ obj: R) { ... }

Inside that method it gets the table name, and builds a SQL statement. There are two ways to get the table name:

R.tableName
type(of: obj).tableName

If I have some class inheritance, those could give different answers, and it seems I'd always want to use the second. I wonder if I'm thinking about this correctly. Maybe I should avoid mixing static type-level protocol requirements with class hierarchies. I could make tableName an instance requirement instead.

Note that the two don’t do the same thing: one is the static type (what is known to the compiler), the other is the dynamic type (what is known to the runtime).
Though if (like often is in a generic function) the two are considered semantically equivalent, I prefer using the static version

At the risk of saying you’re holding it wrong, are you sure you actually want tableName to be a type-level property?

With that design, if you want two different tables with the same types of data, you’ll need to define two separate yet functionally identical types that differ only in the tableName property.

I would make a separate DatabaseTable type, which has tableName as an instance-level property, and is generic over Record: PersistentRecord.

Then you can make as many tables as you like with whatever kinds of data you want, and when you want to make queries you start with a DatabaseTable instance.

1 Like

I see what you mean and how that could happen in certain contexts. But I've found it to be a waste of time to justify/debate application design on forums, when it's background and not directly related to the question. In this case, if you don't like tableName, imagine some other static protocol property. When you mix static requirements with class inheritance, you get this issue. I guess there is no way around it. You just have to know to use type(of: arg).foo in cases where you might have runtime polymorphism. For some reason it just looked ugly to me and I thought maybe I was missing something. I think I'm just not used to mixing the two kinds of "polymorphism" - "generic" and "dynamic", or whatever they're called.

There’s nothing special about protocols here:

class SuperClass {
  class var name: String { "Super" }
}

class SubClass: SuperClass {
  override class var name: String { "Sub" }
}

func foo<T: SuperClass>(_ x: T) {
  print(T.name)
  print(type(of: x).name)
}

foo(SubClass())
// prints:
// Sub
// Sub

foo(SubClass() as SuperClass)
// prints:
// Super
// Sub

Whatever type you specify as T, that’s the type that T represents, so accessing a type property on T will get you the property that T has.

If you’ve upcast a SubClass to a SuperClass, then T is SuperClass.

3 Likes