Introducing TCA Composer - A swift macro framework for eliminating boiler-plate code in TCA Reducers #2766
scogeo
started this conversation in
Show and tell
Replies: 1 comment 1 reply
-
Hi @scogeo, thanks for sharing! The macros certainly pack a punch! We will also be announcing a few updates to |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Intro
I was inspired by the recent Point-Free Macro Bonanza to explore some ideas I have had for some time in reducing boilerplate in TCA-based apps. Macros seemed like the perfect way to accomplish this. The project began with a modest goal of simplifying how to use scopes in TCA. Instead of writing code like
store.scope(state: \.child, action: \.child)
, I wanted to more concisely writestore.scopes.child
. That turned out to be fairly easy to implement and my mind was then filled with possibilities of what else I could accomplish with macros. So I began incrementally adding new capabilities to my macro framework using my own apps as a test bed and playground.The final result is called TCA Composer. I'm happy to release it publicly today after a couple months of development in private. Over the coming days and weeks I will be improving the documentation, adding additional examples, and implementing a few additional capabilities that many may find useful.
Example
To show you how TCA Composer works, let's start with a simple
TwoCounters
example that uses the canonicalCounter
reducer to combining twoCounter
reducers into a single reducer and add some basic functions such as optionally displaying the sum of the two counters and providing a button to reset both counters to zero.First, let's see how this
Reducer
would be implemented normally:While this is very straightforward to implement, it is necessary to make code changes in three spearate places for each child reducer, once in each of
Action
,State
, and thebody
. The changes are trivial, but can be error prone in practice, especially when you introduce optional state, identified arrays, navigation, etc. In addition, complex reducers and SwiftUIView
s can encounter the dreadedThe compiler is unable to type-check this expression in reasonable time;
error from simple typos and syntax errors.Composer simplfies all of this so you can define your child reducers in a single place, and it will generate all of the necessary boilerplate for you. It also generates what I call a
ScopePath
for each child reducer, allowing you to scope stores using dynamic member lookup on a newscopes
operation on an extension ofStore
.Using the
TCAComposer
library here is what theTwoCounters
reducer looks like:Let's see what code was generated. Click to expand the
@Composer
macroWow, that's a lot code! Composer has automatically generated an
Action
that includes conformance forBindingAction
thanks to the.bindable
option. TheAction
also incorporates cases for our two reducer children and theview
action from@ComposeBodyActionCase
macro. The automatically generatedbody
calls theBindingReducer
, scopes the two child reducers and then finally invokes ourview
function to reduce theViewAction
.You will also notice that new macros appear that begin with an underscore are attached to
State
. These are internal macros that Composer uses to generate code in portions of yourReducer
code and are a byproduct of how the swift macro system works. The internal macros are not meant to be used by you and may change from release to release. Here's what they look like when fully expanded forState
:The macros automatically added new members to
State
for our child reducers including the required support for@ObservableState
. The@_ComposerScopePathable
macro combined with the generatedAllComposedScopePaths
struct provides support for improving view ergonomics by generating aScopePath
for each child reducer so that you can scope a child reducer usingstore.scopes.counter1
, rather than the more verbosestore.scope(state: \.counter1, action: \.counter1)
. Pretty cool, eh?Summary
Composer can do this and so much more. For more information, please checkout the the tca-composer repo.
If you have any comments or questions about Composer please post them on my announcement discussion in my repo.
Beta Was this translation helpful? Give feedback.
All reactions