Pullback along enum state

Currently, pullback takes a WritableKeyPath as its state argument. This does not work well with enum state as illustrated in the following example:

enum Content {
  case group(Group)
  case item(Item)
}

enum ContentAction {
  case group(GroupAction)
  case item(ItemAction)
}

let contentReducer = Reducer<RequestCollection.Content, ContentAction, Void>.combine(
  groupReducer.pullback(state: \Content.group, action: /ContentAction.group, environment: { $0 }),
  requestReducer.pullback(state: \Content.request, action: /ContentAction.request, environment: { $0 })
)

This contentReducer won't compile as WritableKeyPaths are not automatically generated for enum cases. To work around this issue one could write get/set properties for each enum case manually:

extension Content {
  var group: Group? {
    get {
      guard case let .group(group) = self else { return nil }
      return group
    } set {
      guard let group = newValue else { return }
      self = .group(group)
    }
  }

  // request property left out for brevity
}

As you can see, this property has to be Optional in "case" the Content is not a Group. This means that the pullback would actually have to use the .optional groupReducer and .optional requestReducer:

let contentReducer = Reducer<RequestCollection.Content, ContentAction, Void>.combine(
  groupReducer.optional.pullback(state: \Content.group, action: /ContentAction.group, environment: { $0 }),
  requestReducer.optional.pullback(state: \Content.request, action: /ContentAction.request, environment: { $0 })
)

However, in the Composable Architecture this has already been solved with the help of CasePaths. I believe there should be an option to pullback local state along a CasePath as well.

4 Likes

Thanks for starting the discussion! Anyone that wants to catch up with the previous discussion on GitHub issues can do so here: https://github.com/pointfreeco/swift-composable-architecture/issues/69

1 Like

@stephencelis do you think that if the language had native support for CasePaths it could be done with the same type that KeyPath and thus offer access to structs and enums at the same time?

If Swift had native support for case paths and writable optional-chained paths we could leverage that, yep! In the meantime we can close the gap ourselves, but it requires a little more tooling. I have a draft PR exploring this here if you want to give it a spin! https://github.com/pointfreeco/swift-composable-architecture/pull/82

It also updates the Tic-Tac-Toe demo to have enum state for logged-in vs. logged-out to show how pulling back along enum state looks in practice.

2 Likes