Skip to content

Instantly share code, notes, and snippets.

@Qata
Last active August 6, 2019 05:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Qata/d6b8f62ff12fdf948f3520f6fe6d2081 to your computer and use it in GitHub Desktop.
Save Qata/d6b8f62ff12fdf948f3520f6fe6d2081 to your computer and use it in GitHub Desktop.
Recombine
/**
Middleware is a structure that allows you to modify, filter out and dispatch more
actions, before the action being handled reaches the store.
*/
public struct Middleware<State, Action> {
typealias Transform = (State, Action) -> [Action]
internal let transform: (State, Action) -> [Action]
/// Create a blank slate Middleware.
public init() {
self.transform = { [$1] }
}
/**
Initialises the middleware with a transformative function.
- parameter transform: The function that will be able to modify passed actions.
*/
internal init(_ transform: @escaping Transform) {
self.transform = transform
}
/**
Initialises the middleware by concatenating the transformative functions from
the middleware that was passed in.
*/
public init(_ first: Middleware, _ rest: Middleware...) {
self = .init(first, rest)
}
/**
Initialises the middleware by concatenating the transformative functions from
the middleware that was passed in.
*/
public init<S: Sequence>(_ first: Middleware, _ rest: S) where S.Element == Middleware {
self = rest.reduce(first) {
$0.concat($1)
}
}
/// Safe encapsulation of side effects guaranteed not to affect the action being passed through the middleware.
public func sideEffect(_ effect: @escaping (State, Action) -> Void) -> Middleware {
.init { state, action in
self.transform(state, action).map {
effect(state, $0)
return $0
}
}
}
/// Concatenates the transform function of the passed `Middleware` onto the callee's transform.
public func concat(_ other: Middleware) -> Middleware {
.init { state, action in
self.transform(state, action).flatMap {
other.transform(state, $0)
}
}
}
/// Transform the action into another action.
public func map(_ transform: @escaping (State, Action) -> Action) -> Middleware {
.init { state, action in
self.transform(state, action).map {
transform(state, $0)
}
}
}
/// One to many pattern allowing one action to be turned into multiple.
public func flatMap<S: Sequence>(_ transform: @escaping (State, Action) -> S) -> Middleware where S.Element == Action {
.init { state, action in
self.transform(state, action).flatMap {
transform(state, $0)
}
}
}
/// Filters while mapping actions to new actions.
public func filterMap(_ transform: @escaping (State, Action) -> Action?) -> Middleware {
.init { state, action in
self.transform(state, action).compactMap {
transform(state, $0)
}
}
}
/// Drop the action iff `isIncluded(action) != true`.
public func filter(_ isIncluded: @escaping (State, Action) -> Bool) -> Middleware {
.init { state, action in
self.transform(state, action).filter {
isIncluded(state, $0)
}
}
}
}
public struct Reducer<State, Action> {
public typealias Transform = (_ state: State, _ action: Action) -> State
let transform: Transform
public init(_ transform: @escaping Transform) {
self.transform = transform
}
public static func concat(_ first: Reducer<State, Action>, _ rest: Reducer<State, Action>...) -> Reducer<State, Action> {
return concat(first, rest)
}
public static func concat<S>(_ first: Reducer<State, Action>, _ rest: S) -> Reducer<State, Action> where S: Sequence, S.Element == Reducer<State, Action> {
return .init { state, action in
rest.reduce(first.transform(state, action)) { state, reducer in
reducer.transform(state, action)
}
}
}
}
import Combine
open class Store<State, Action>: ObservableObject, Subscriber {
@Published public private(set) var state: State
private let actions = PassthroughSubject<Action, Never>()
private var cancellables = Set<AnyCancellable>()
public required init(state: State, reducer: Reducer<State, Action>, middleware: Middleware<State, Action> = .init()) {
self.state = state
actions.scan(state) { state, action in
middleware
.transform(state, action)
.reduce(state, reducer.transform)
}
.sink(receiveValue: { [unowned self] state in
self.state = state
})
.store(in: &cancellables)
}
open func dispatch(_ actions: Action...) {
dispatch(actions)
}
open func dispatch<S: Sequence>(_ actions: S) where S.Element == Action {
actions.forEach(self.actions.send)
}
public func receive(subscription: Subscription) {
subscription.store(in: &cancellables)
subscription.request(.unlimited)
}
public func receive(_ input: Action) -> Subscribers.Demand {
dispatch(input)
return .unlimited
}
public func receive(completion: Subscribers.Completion<Never>) {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment