Dynamic typealiases on enum

Hi everyone ;-)

Let's say I have an enum

enum MyEnum {
   case one, two
}

I would like to create a function in my enum that returns a dynamic type depending on the value of the enum like this

extension MyEnum {
   var myObject: T {
      switch self {
         case .one: return ObjectOne()
         case .two: return ObjectTwo()
      }
   }
}

But (there's always a But...), I don't want to cast the receiver like

let _: ObjectOne = MyEnum.one.myObject

or

let _: MyEnum<ObjectOne> = .one.myObject

The kind of behavior that I would really like would be a dynamic typealias (this does not work of course)

extension MyEnum {
   typealias T = {
      switch self {
         case .one: return ObjectOne.self
         case .two: return ObjectTwo.self
      }
   } ()
}

// that would allow me to write this and _ would be of type ObjectOne
let _ = MyEnum.one.myObject

I tried protocols with associated types and static objects instead of enums, I tried nested protocol inside enums, I can't find a way to make it work

Any idea, or is this just no achievable ? Thanks 🙏

Answered by DTS Engineer in 758680022

Yes, I understand. I've run into similar situations before, and I think the short answer is that an enum probably isn't going to be the best solution. Enums are normally great because they're convenient to use, but the fact that the "conceal" type information, at compile time at least, makes them too simplistic for a use-case like this.

Given your 3 video types (YoutubeVideo etc), maybe a better solution would be to make a generic VideoManager concrete type (not a protocol) whose generic parameter is a type conforming to Video protocol. Then your factory function could look something like this:

struct VideoFactory {
   func videoManager<T>(for videoType: T.Type) -> VideoManager<T> where T: Video {}
}
let youtubeVideoManager = .factory.videoManager(for: YoutubeVideo.self)

Since ObjectOne and ObjectTwo are unrelated types (they don't even share conformance to a protocol), you're effectively trying to use polymorphism in a language that (mostly) strongly typed, and that's not going to work out well.

Specifically, though, your requirements conflict. If you are going to use .one literally (in MyEnum.one.myObject) then the enum isn't really doing any work for you. You may as well just say ObjectOne() directly. Even if there's some additional context that needs to be buried inside an enum, you could have a static enum property such as MyEnum.objectOne that returns a suitable ObjectOne.

OTOH, if you're not going to use .one literally, so you have a variable containing some enum case that you don't know in advance, there's no way the compiler can type the result as ObjectOne or ObjectTwo, since that information isn't known at compile time.

So, this is not really a workable approach, in the terms you've used to describe it. This is more of a "what are you really trying to do" question, in terms of type usage? Whether you're just trying to reduce the number of keystrokes, or there's a larger conceptual goal, I don't think you've landed on the right question yet. 🙂

Thanks @Polyphonic, but I guess it requires a little bit more of context.

I can't say too much for NDA reasons, but I'll try to make an analogy ;-) Let's say I have a Video framework that allows me to create video objects. Theses videos can be of certain type (Local, Youtube, Streamed). Each Video is not related (that's my first issue, but since I have to use this framework, I have no other way to tackle this). Add on top that this framework is an objC one 😬

Now let's say that I'm building a framework that allows me to load the various Video objects, and I want to leverage as much of Swift capabilities. To do so, I started by creating an enum VideoType that "mirrors" the separated objC objects. So I have this

enum VideoType {
   case local, youtube, streamed
}

Now, I create VideoManager objects that relies on a protocol that allows me to load and show a video. But, I want this objects to handle the underlying objC Video Objects. To do so, I used an associatedType

protocol VideoManager {
   associatedtype Video
   func load()
   func show()
   var video: Video { get set }
}

Now I create dedicated objects that implement the behind-the-scene logic with the proper Video objects, one for each enum value. Here's one for the sake of it

struct YoutubeVideoManager: VideoManager {
   typealias Video = YoutubeVideo
   func load() {}
   func show() {}
   var video: Video { ... }
}

Now, here is the part where it fails. I'd like to create a factory like object that would return the actual VideoManager depending on the enum

struct VideoFactory {
   func videoManager(for videoType: VideoType) -> VideoManager {}
}

But in this example, it returns a VideoManager, not an actual implementation. I'd like that when I call videoManager(for: .youtube), it returns with a typesafe check a YoutubeVideoManager and if I try to call it like let _: LocalVideoManager = factory.videoManager(for: .youtube, then I get an error from compilation.

So far, the only thing I managed to do is to add a generic to the enum enum VideoType<T: VideoManager> and check at runtime that T is of type YoutubeVideoManager for the .youtube value, otherwise I throw an error. But it's a runtime check AND I have to declare my variables like this let _: = .factory.videoManager(for: VideoType<YoutubeVideoManager>.youtubewhich is pretty ugly I agree 😅

I hope I gave you enough context. I'm pretty sure it's not actually doable, but maybe I'm so stuck on that "dynamic typealias" that there is another solution more obvious that I don't see!

Thanks @Polyphonic

Accepted Answer

Yes, I understand. I've run into similar situations before, and I think the short answer is that an enum probably isn't going to be the best solution. Enums are normally great because they're convenient to use, but the fact that the "conceal" type information, at compile time at least, makes them too simplistic for a use-case like this.

Given your 3 video types (YoutubeVideo etc), maybe a better solution would be to make a generic VideoManager concrete type (not a protocol) whose generic parameter is a type conforming to Video protocol. Then your factory function could look something like this:

struct VideoFactory {
   func videoManager<T>(for videoType: T.Type) -> VideoManager<T> where T: Video {}
}
let youtubeVideoManager = .factory.videoManager(for: YoutubeVideo.self)
Dynamic typealiases on enum
 
 
Q