POC: generic effects #732
peter-tomaselli
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Hi! This was originally a PR with a proof of concept of a new effects thingy, but it was suggested that a discussion would be a better place for this. Here is the diff. The origin PR text follows. Please let me know if there is any additional fancy GitHubbery that could be used to enhance this post 😄
---Afternoon! This is a very experimental PR that is not at all production ready, and is very possibly not the direction y’all have been envisioning for this project. However, I do think it is interesting, and perhaps it could trigger some interesting discussion!
This PR re-defines
Reducerto be generic over itsEffectstype. (Well, in this PR, just to keep this diff small, a new type called_Reduceris introduced, andReduceris then redefined as atypealiasfor the previous shape. This is just for convenience of implementing the POC and is probably not the right way to do this “for real”).Mechanically speaking, the next thing that happens is that reducer operators get floated “upwards” to live in less-constrained extensions. Two new protocols,
HasNoneandHasMerge(each given a deliberately bad name 😅) are introduced to support this. Some reducer operators can’t be generalized easily, so they continue to live in an extension where_Reducer.Effects == Effect<Action, Never>. (Just to be clear at this point,Effectsis the new generic parameter introduced as part of this PR, andEffectis the existing TCA effect type. The constraint just mentioned recovers existing TCA behavior after the new generic initially breaks it.)At this point there is some syntactic noise because writing an
extensionon atypealiasdoesn’t work well in Swift for some reason, but after fixing that, we can run the existing tests and they pass!Now the fun part. We can start supporting generic effects. We’ll introduce this function:
This function “interprets” a
_Reducerinto a new effects type by providing a two mappings, one a pullback-flavored mapping from someGlobalEnvironmentinto the localEnvironment, and one covariant mapping into aNewEffectstype. Very sneakily, the global environment is available during this second mapping.Practically speaking, this lets us write reducers where the
Effectsare defunctionalized and can be represented by any value. We can then, at a time of our choosing,interpretsuch reducers into a functionalized or “terminal” effect type (e.g. TCAEffects) and use them in existing stores.This opens up some interesting possibilities! I don’t have a ton of case studies, but I tried to add a couple. One is, sometimes a simple
structcan be used to represent an effect. I updated the Counter example to send “analytics”; while the analytics reducer is layered on to the business logic reducer in a way we are already used to, the core of the analytics reducer doesn’t have to know anything about how to send effects. Its “effect type” is just this:It’s worth noting again that the defunctionalized
counterAnalyticsReducerhas aVoidenvironment now; it no longer needs to know how to actually send the effects. I think this is particularly neato — writing a reducer where the effects are just a simple data type usually means that that reducer needs radically fewer dependencies than before.I also updated the Effects Basics reducer in a similar way: the main body of the reducer now only knows about an
enum EffectsBasicsEffectand no longer needs any dependencies. Just before using this reducer, weinterpretit to rebuild the original TCAEffects.There’s one other snippet in here that is basically just a sketch:
_Reducer.replaceErrorcan be used (I think! I have only just started playing with this) to turn a reducer where theEffect.Failureis notNeverinto a reducer where it is, by replacing all Combine errors with a providedaction(e.g.FooAction.displayError("there was an error")). So,replaceErrorbut at theReducerlevel.It’s possible that this technique could be used to support other “terminal” effects types as well. For example, my colleague @srgisme and I use this construction in an in-house library (which is heavily inspired by TCA, in case it wasn’t already obvious!) to support RxSwift for effects as well. (At the store level, there’s nothing fancy to this, we just have two different kinds of
Stores.)Thanks for the great library and all the awesome content! I hope this PR at least provides a fun Friday read!
Beta Was this translation helpful? Give feedback.
All reactions