Replies: 7 comments 9 replies
-
@mackoj this branch is pretty awesome. Converting existing code to use I haven't really thought about the pros/cons of each approach and was probably too excited to really be objective when I was testing it out though :P Interested to hear how other people's experiences are :) |
Beta Was this translation helpful? Give feedback.
-
This is something I was experimenting with awhile back as well, so I’m excited Brandon and Stephen have been looking into it more as well! I’m especially interested in the ways dependencies are structured; it makes it even more of a parallel to SwiftUI. I think there may be an opportunity as well in having dependencies propagate down the Reducer hierarchy in a way that mirrors SwiftUI’s Environment propagation, though I’m not certain how this might be accomplished. I have some ideas, but I’ll need to sit down and play with them. I was also experimenting with a couple of other ideas recently after browsing through this branch:
Ideally that'd all look something like: protocol Reducer {
associatedtype State
associatedtype Action
associatedtype Child: Reducer where Child.State == State, Child.Action == Action
associatedtype Effect: AsyncSequence where Effect.Element == Action
var child: Child { get }
func apply(_ action: Action, to state: inout State)
func fireEffects(for action: Action, with state: State) -> Effect
}
extension Reducer {
func apply(_ action: Action, to state: inout State) {}
}
extension Reducer where Child == EmptyReducer<State, Action> {
var child: Child { EmptyReducer() }
}
extension Reducer where Effect == EmptyAsyncSequence<Action> {
func fireEffects(for action: Action, with state: State) -> Effect {
EmptyAsyncSequence<Action>()
}
}
struct AppReducer: Reducer {
@ReducerBuilder<State, Action> // builder syntax will implicitly merge reducers
var children: some Reducer<Action == AppAction, State == AppState> {
SystemReducer()
.pullback(/* ...*/)
.optional()
SettingsReducer(client: someExplicitDependency)
.pullback(/* ... */)
Feature1Reducer()
.forEach(/* ... */)
.dependency(/* ... */)
}
func apply(_ action: AppAction, to state: inout AppState) {
switch action {
case .doThing:
state.thing = "thing"
// etc...
}
}
@AsyncSequenceBuilder<Action> // builder syntax implicitly creates conditional sequences
func fireEffects(for action: AppAction, with state: AppState) -> some AsyncSequence<Element == Action> {
switch action {
case .didLaunch:
self.someDependency.didLaunchAsyncSequence()
.flatMap(/*...*/)
.map(AppAction.blahblah)
default:
EmptyAsyncSequence()
}
}
} Again, a lot of that isn't yet possible, and I don't know how ideal a lot of it is. Some of it is possible if one is will to, as we currently do, 'erase' the sequence to a universal, solid, generic There’s a lot of fun to be had in this arena; I’m interested to see what, if anything, ends up sticking! |
Beta Was this translation helpful? Give feedback.
-
@mackoj Nice sleuthing! 😆 We're excited about the branch and what it's already unlocking, but it's definitely early days and there's a lot to figure out! The awkward interplay between top-level composed reducers (which we house in statics like If anyone comes up with any interesting ideas or problems with the current branch, please do share! |
Beta Was this translation helpful? Give feedback.
-
One concern I have with TCA is the huge memory traffic it can generate when composing and reducing states values in complex applications with deep nesting of features. I proposed something I believed would alleviate this in #603, but I was unaware of the copy-in/copy-out behavior of One similar solution would be to pass to the reducer something wrapping the Let define a @dynamicMemberLookup
public struct Snapshot<State> {
public var state: State {
didSet { stateDidChange = true }
}
var stateDidChange: Bool = false
init(state: State) {
self.state = state
}
public subscript<Value>(dynamicMember keyPath: KeyPath<State, Value>) -> Value {
state[keyPath: keyPath]
}
public subscript<Value>(dynamicMember keyPath: WritableKeyPath<State, Value>) -> Value {
get { state[keyPath: keyPath] }
set { state[keyPath: keyPath] = newValue }
}
} This wrapper type is passed to the reducer: public protocol _Reducer {
associatedtype State
associatedtype Action
func reduce(into state: inout Snapshot<State>, action: Action) -> Effect<Action, Never>
} Anything writing the state in As the public struct Snapshot<State, Action> {
public var state: State {
didSet { stateDidChange = true }
}
public var effect: Effect<Action, Never>? = nil
var stateDidChange: Bool = false
init(state: State) {
self.state = state
}
}
public protocol _Reducer {
associatedtype State
associatedtype Action
func reduce(snapshot: inout Snapshot<State, Action>, action: Action) -> Void
} To produce an effect, we simply assign the snapshot's Most importantly, this struct would capture the complete unit of work a reducer produces, and I have a feeling that this could be useful in async contexts, or when exploiting optimization opportunities the protocolized version of TCA would offer, for example. What do you think about this? |
Beta Was this translation helpful? Give feedback.
-
Has someone tried to use TaskLocal to store the environment ? |
Beta Was this translation helpful? Give feedback.
-
Swift team in their Big Picture essay on string parsing, specifically talk about "event processing", that is using Swift's parsing abilities to handle and respond to events and how that generalises to application logic handling. Maybe someone will find it interesting here. |
Beta Was this translation helpful? Give feedback.
-
Would be nice to remove |
Beta Was this translation helpful? Give feedback.
-
Hi,
The work on the branch proto is interesting because it tackles multiple issues that I faced often when using TCA.
https://github.com/pointfreeco/swift-composable-architecture/compare/proto?expand=1
From what I understand
Environment
is removed and is replaced by a property wrapper@Dependency
.@Dependency
in the viewDo you think that the work on this branch is interesting?
What other advantages/inconvenient that you see with this approach?
Does anyone tried to use this in his/her codebase ?
Thanks,
Beta Was this translation helpful? Give feedback.
All reactions