[Completing Generics] Opening existentials — A use case: type-safe dependency injection container


(Adam Sharp) #1

One explicit way to allow such operations in a type-safe manner is to introduce an “open existential” operation of some sort, which extracts and gives a name to the dynamic type stored inside an existential. For example:

  if let storedInE1 = e1 openas T { // T is a the type of storedInE1, a copy of the value stored in e1
    if let storedInE2 = e2 as? T { // is e2 also a T?
      if storedInE1 == storedInE2 { … } // okay: storedInT1 and storedInE2 are both of type T, which we know is Equatable
    }
  }

If I'm not mistaken, this topic was in the "maybe" section, and I'd like to offer a concrete use case.

A while back I attempted to extend `UIStoryboard` to be a type-safe dependency injection container. For example, given a protocol like this:

  protocol Presenter: class {
    associatedtype Data // Don't worry too much about this name, it's probably overly generic

    // Called by the dependency injection container after initialisation to inject the presented data.
    func updateWithData(data: Data)
  }

And then a view controller could declare itself to be a `Presenter`:

  class ProfileViewController: Presenter {
    typealias Data = Profile

    private var profile: Profile?

    func updateWithData(profile: Profile) {
      self.profile = profile
      updateDisplay()
    }
  }

And then when setting up your storyboard, you would register various factory closures to fetch this data on demand:

  let storyboard = DIStoryboard(name: "Profile", registerDependencies: { dependencies in
    dependencies.add { () -> Profile in
      return profileCache.currentUserProfile
    }
  })

To glue these pieces together, you'd need a way to declare an existential type for the current view controller, and then match that against one of the registered factory functions:

  // DIStoryboard.swift
  private func updateViewController(viewController: UIViewController) {
    if let <P: Presenter> presenter = viewController as? P {
      // Now what? Possibly:
      // 1. Iterate over each factory function
      // 2. When I find a function () -> P.Data, call it and pass the result to viewController.updateWithData()
    }
  }

Is this a good idea, in general? I don't really know, because I couldn't really complete the experiment!

But I have a feeling that there might be other cases where opening existentials could allow for more type-safe code when wrapping UIKit functionality. Currently, Swift generics are completely incompatible with the way objects are unarchived from storyboards and nibs, and it feels like this feature _could_ help to bridge the gap. I have a feeling a lot of Swift users would like to bring more of Swift's type safety to their Cocoa code.

Anyway, I'd love to hear some feedback about this idea!

–Adam