Best ways to get type information back after it's been "erased"?

Lets say I have some type, Property<Value>, and I want to type-erase this to AnyProperty so that I can now have Array<AnyProperty>, and then, have a typeUnerasedProperty variable on AnyProperty such that I can do:

func process(properties: [AnyProperty]) {
    properties.map {
        let typeUnerasedProperty = $0.unerase
        process(property: typeUnerasedProperty)
    }
}

func process<T>(property: Property<T>) where T == Int {
    // do stuff when T is Int
}

func process<T>(property: Property<T>) where T == Double {
    // do stuff when T is Double
}

func process<T>(property: Property<T>) {
    // do stuff when T isn't Int or Double
}

Is this possible at all within Swift? Thanks.

Note: I have seen that we can save the type itself as a runtime variable but there doesn't seem to be a way to take advantage of that within the generics system, which seems to just be limited to only affecting static behaviors.

1 Like

You cannot get type information back. Instead, embed the necessary dynamism before you erase the type.

protocol Processable {
  func customizedProcessing()
}
extension Processable {
  func customizedProcessing() {
    // do stuff when Self isn’t Int or Double
  }
}

extension Int {
  func customizedProcessing() {
    // Do stuff when Self is Int
  }
}

extension Double {
  func customizedProcessing() {
    // Do stuff when Self is Double
  }
}

struct Property<Value> where Value: Processable {
  var value: Value
  func process() {
    value.customizedProcessing()
  }
}

struct AnyProperty {
  init<Value>(_ property: Property<Value>) {
    processClosure = { property.process() }
  }
  private let processClosure: () -> Void
  func process() {
    processClosure()
  }
}

func process(properties: [AnyProperty]) {
  _ = properties.map { property in
    property.process()
  }
}
3 Likes

Seems the key to effective use of type erasure with generics in Swift is defining your API from the beginning in terms of actions as opposed to defining them in terms of data, so that later when you need to do type erasure, you can unerase things via capture semantics.

If you've already designed the API around data then it can become awfully hard to pry yourself free from using existentials.

1 Like

There's no need to define ~= for types, you can use case is Int.Type: (or, with a value, case is Int:). Also, you are not really using _openExistential since at the end you are switching over the value type. You can simplify your process(properties:) function to be just:

func process(properties: [AnyProperty]) {
  for property in properties {
    switch property.value {
    case let value as Int:
      process(property: value)
    case let value:
      process(property: value)
    }
  }
}
2 Likes

Here is an example (should be understandable just by this 1 file): TypedNotificationCenter/SameTypedNotification.swift at master · Cyberbeni/TypedNotificationCenter (github.com)

Edit: This is basically the same as the solution by @SDGGiesbrecht

Edit2: You can probably write a Sourcery template for this if you need to have multiple functions be available on the type erased type.