Accessing the associated value of an enum

I have an enum like this:

enum AppModel {
    case loading
    case onboarding(OnboardingModel)
    case signedOut(SignInModel)
}

Now I have a Signal<AppModel> (CwlSignal in this case, but I don’t think it matters). The signal supports the usual map method that takes (A) -> B and turns a Signal<A> to Signal<B>. To create an “onboarding signal”, I currently use this:

let subSignal = appSignal.map { (appModel: AppModel) -> OnboardingModel? in
    guard case let .onboarding(model) = appModel else { return nil }
    return model
}

Which isn’t nice at all. Is it possible to extract the sub-signal in a nicer, more generic fashion? (I think I would like something akin to SR-5822.)

What is Signal? Is it something from RxCocoa?

Sort of, it’s CwlSignal. But I think the question would work the same way even with something like Result.map. The issue is selecting a case from an enum with a decent syntax.

Can you elaborate a little more about the issue you've linked. I'm having hard time understating what you want to achieve. The if block in the issue is no different from your guard.

For instance you could create computed properties on your enum that would extract the associated type or return nil instead. That would make your map function more readable.

Edit: I see the issue does not even compile. Okay then I get it now.

1 Like

If this were a product type instead:

struct AppModel {
    var onboardingState: OnboardingState?
    var signInState: SignInState?
}

I could create the sub-signal quite easily:

let subSignal = appSignal.map { $0.onboardingState }

(Even in generic fashion, thanks to key paths!) I wish something like that would be possible with sum types. I could add computed properties to solve the issue indeed:

public extension AppModel {
    public var onboardingModel: OnboardingModel? {
        guard case .onboarding(let model) = self else { return nil }
        return model
    }
}

But that’s… dumb. Maybe I am mistaken, but it feels like this should be the compiler’s job, not mine? With the current feature disparity between sum and product types, it’s quite tempting to just resign and resort to unsafe emulation:

struct AppModel {

    enum State {
        case loading
        case onboarding
        case signedOut
    }

    var state: State
    // only valid for .onboarding
    var onboardingModel: OnboardingModel?
    // only valid for .signedOut
    var signInModel: SignInModel?
}

This way I get Codable for free, can use key paths, can easily compare cases ignoring the associated values and generally get better syntax. I hate to lose the type-level guarantees, though.

It totally depends on the surrounding architecture you're building. For instance I have a typealias and a protocol to be able to extract the associated types and fake generic covariance in a switch.

typealias EventContainer<Namespace> = (
  event: Namespace.Event,
  coordinator: Namespace.Coordinator
) where Namespace : EventArchitecture

// `handle` functions are overloaded
switch (event, coordinator) {
case let container as EventContainer<Onboarding>:
  handle(container.event, from: container.coordinator)
case let container as EventContainer<Checklist>:
  handle(container.event, from: container.coordinator)
}

I also have a type that stores the information I need only in the generic type parameter list. Those types are bound to some protocols so I can extract that type from the generic parameter list later (there is no type member that uses the type - it's entirely stored only between < ... >).

You can imagine something like this (but far more complicated):

struct MyType<T> where T : SomeProtocol {
   init(_: T.Type = T.self) {}
}

This allows me to have to build a type safe architecture.

1 Like

I think you might want to look at this discussion.

4 Likes