Skip to content

Instantly share code, notes, and snippets.

@fellipecaetano
Last active January 15, 2018 20:08
Show Gist options
  • Save fellipecaetano/7a343ea7794ddf96025a8ddcc1240e54 to your computer and use it in GitHub Desktop.
Save fellipecaetano/7a343ea7794ddf96025a8ddcc1240e54 to your computer and use it in GitHub Desktop.
Extending Redux with an effect system
/* Credits to @gsampaio for coming up with the Effect<S, A> type.
* Even though this flavor of reducer also describes side-effects,
* testing is still trivial since it's sufficient to check if the
* side-effects are of the right type. The actual execution of the
* effect can be tested separately using integration tests. */
import RxSwift
class Effect<S: Equatable, A: Action> {
open func execute(state: () -> S) -> Observable<A> {
fatalError("not implemented")
}
static func empty<S, A>() -> Effect<S, A> {
return EmptyEffect()
}
private class EmptyEffect<S: Equatable, A: Action>: Effect<S, A> {
override func execute(state: () -> S) -> Observable<A> {
return .empty()
}
}
}
protocol Action {}
struct Reducer<S: Equatable, A: Action> {
let reduce: (S, A) -> (S, Effect<S, A>)
init (_ reduce: @escaping (S, A) -> (S, Effect<S, A>)) {
self.reduce = reduce
}
}
class Store<S: Equatable, A: Action> {
private let actionSink: AnyObserver<A>
private let state: Observable<S>
fileprivate init (actionSink: AnyObserver<A>, state: Observable<S>) {
self.actionSink = actionSink
self.state = state
}
static func create<S, A>(reducer: Reducer<S, A>, initialState: S) -> Store<S, A> {
let actionSink: AnyObserver<A>
let stateSubject = BehaviorSubject<S>(value: initialState)
var dispatch: (A) -> Void = { _ in }
var interpret: (Effect<S, A>) -> Void = { _ in }
var currentState = initialState {
didSet { stateSubject.onNext(currentState) }
}
actionSink = AnyObserver<A> { event in
switch event {
case let .next(action): dispatch(action)
default: break
}
}
dispatch = { action in
let (nextState, nextEffect) = reducer.reduce(currentState, action)
currentState = nextState
interpret(nextEffect)
}
interpret = { effect in
_ = effect.execute(state: { currentState }).subscribe(onNext: dispatch)
}
return Store<S, A>(actionSink: actionSink, state: stateSubject)
}
func asObserver() -> AnyObserver<A> {
return actionSink
}
func asObservable() -> Observable<S> {
return state
}
}
//
class PrintEffect: Effect<PrintState, PrintAction> {
override func execute(state: () -> PrintState) -> Observable<PrintAction> {
print("State before effect:", state())
return .just(.finishPrinting)
}
}
enum PrintAction: Action {
case startPrinting
case finishPrinting
}
struct PrintState: Equatable {
let isPrinting: Bool
static func == (lhs: PrintState, rhs: PrintState) -> Bool {
return lhs.isPrinting == rhs.isPrinting
}
}
func PrintReducer() -> Reducer<PrintState, PrintAction> {
return Reducer { _, action in
switch action {
case .startPrinting:
return (PrintState(isPrinting: true), PrintEffect())
case .finishPrinting:
return (PrintState(isPrinting: false), .empty())
}
}
}
let store = Store<PrintState, PrintAction>.create(reducer: PrintReducer(), initialState: PrintState(isPrinting: false))
_ = store.asObservable().subscribe(onNext: { state in print("Subscribed state:", state) })
store.asObserver().onNext(.startPrinting)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment