/* * Fluxor * Copyright (c) Morten Bjerg Gregersen 2020 * MIT license, see LICENSE file for details */ import Foundation // swiftlint:disable function_parameter_count /// Something which selects a `Value` from the specified `State`. public protocol SelectorProtocol { /// The input for the `Selector`. associatedtype State /// The output of the `Selector`, associatedtype Value /** A pure function which takes a `State` and returns a `Value` from it. - Parameter state: The `State` to map - Returns: The `Value` mapped from the `State` */ func map(_ state: State) -> Value } /** A type which takes a `State` and returns a `Value` from it. `Selector`s can be based on other `Selector`s making it possible to select a combined `Value`. */ public class Selector: SelectorProtocol { /// An unique identifier used when overriding the `Selector` on the `MockStore`. public let id = UUID() /// The closue used for the mapping. private let _projector: (State) -> Value /// The latest value for a state hash. internal private(set) var result: (stateHash: UUID, value: Value)? /** Creates a `Selector` from a `keyPath`. - Parameter keyPath: The `keyPath` to create the `Selector` from */ public convenience init(keyPath: KeyPath) { self.init(projector: { $0[keyPath: keyPath] }) } /** Creates a `Selector` from a `projector` closure. - Parameter projector: The `projector` closure to create the `Selector` from */ public init(projector: @escaping (State) -> Value) { _projector = projector } public func map(_ state: State) -> Value { _projector(state) } } /// A `Selector` created from a `Selector`s and a `projector` function. public class Selector1: Selector where S1: SelectorProtocol, S1.State == State { /// A pure function which takes the `Value` from the other `Selector` and returns a new `Value`. public let projector: (S1.Value) -> Value /** Creates a `Selector` from a `Selector` and a `projector` function. - Parameter selector1: The first `Selector` - Parameter projector: The closure to pass the value from the `Selector` to */ public init(_ selector1: S1, _ projector: @escaping (S1.Value) -> Value) { self.projector = projector super.init(projector: { projector(selector1.map($0)) }) } /** Creates a `Selector` from a `Selector` and a `projector` function. - Parameter selector1: The first `Selector` - Parameter projector: The closure to pass the value from the `Selector` to */ public convenience init(_ selector1: S1, keyPath: KeyPath) { self.init(selector1) { $0[keyPath: keyPath] } } } /// A `Selector` created from two `Selector`s and a `projector` function. public class Selector2: Selector where S1: SelectorProtocol, S1.State == State, S2: SelectorProtocol, S2.State == State { /// A pure function which takes the `Value`s from the other `Selector`s and returns a new `Value`. public let projector: (S1.Value, S2.Value) -> Value /** Creates a `Selector` from two `Selector`s and a `projector` function. - Parameter selector1: The first `Selector` - Parameter selector2: The second `Selector` - Parameter projector: The closure to pass the values from the `Selector`s to */ public init(_ selector1: S1, _ selector2: S2, _ projector: @escaping (S1.Value, S2.Value) -> Value) { self.projector = projector super.init(projector: { projector(selector1.map($0), selector2.map($0)) }) } } /// A `Selector` created from three `Selector`s and a `projector` function. public class Selector3: Selector where S1: SelectorProtocol, S1.State == State, S2: SelectorProtocol, S2.State == State, S3: SelectorProtocol, S3.State == State { /// A pure function which takes the `Value`s from the other `Selector`s and returns a new `Value`. public let projector: (S1.Value, S2.Value, S3.Value) -> Value /** Creates a `Selector` from three `Selector`s and a `projector` function. - Parameter selector1: The first `Selector` - Parameter selector2: The second `Selector` - Parameter selector3: The third `Selector` - Parameter projector: The closure to pass the values from the `Selectors` to */ public init(_ selector1: S1, _ selector2: S2, _ selector3: S3, _ projector: @escaping (S1.Value, S2.Value, S3.Value) -> Value) { self.projector = projector super.init(projector: { projector(selector1.map($0), selector2.map($0), selector3.map($0)) }) } } /// A `Selector` created from four `Selector`s and a `projector` function. public class Selector4: Selector where S1: SelectorProtocol, S1.State == State, S2: SelectorProtocol, S2.State == State, S3: SelectorProtocol, S3.State == State, S4: SelectorProtocol, S4.State == State { /// A pure function which takes the `Value`s from the other `Selector`s and returns a new `Value`. public let projector: (S1.Value, S2.Value, S3.Value, S4.Value) -> Value /** Creates a `Selector` from four `Selector`s and a `projector` function. - Parameter selector1: The first `Selector` - Parameter selector2: The second `Selector` - Parameter selector3: The third `Selector` - Parameter selector4: The fourth `Selector` - Parameter projector: The closure to pass the values from the `Selectors` to */ public init(_ selector1: S1, _ selector2: S2, _ selector3: S3, _ selector4: S4, _ projector: @escaping (S1.Value, S2.Value, S3.Value, S4.Value) -> Value) { self.projector = projector super.init(projector: { projector(selector1.map($0), selector2.map($0), selector3.map($0), selector4.map($0)) }) } } /// A `Selector` created from five `Selector`s and a `projector` function. public class Selector5: Selector where S1: SelectorProtocol, S1.State == State, S2: SelectorProtocol, S2.State == State, S3: SelectorProtocol, S3.State == State, S4: SelectorProtocol, S4.State == State, S5: SelectorProtocol, S5.State == State { /// A pure function which takes the `Value`s from the other `Selector`s and returns a new `Value`. public let projector: (S1.Value, S2.Value, S3.Value, S4.Value, S5.Value) -> Value /** Creates a `Selector` from five `Selector`s and a `projector` function. - Parameter selector1: The first `Selector` - Parameter selector2: The second `Selector` - Parameter selector3: The third `Selector` - Parameter selector4: The fourth `Selector` - Parameter selector5: The fifth `Selector` - Parameter projector: The closure to pass the values from the `Selectors` to */ public init(_ selector1: S1, _ selector2: S2, _ selector3: S3, _ selector4: S4, _ selector5: S5, _ projector: @escaping (S1.Value, S2.Value, S3.Value, S4.Value, S5.Value) -> Value) { self.projector = projector super.init(projector: { projector(selector1.map($0), selector2.map($0), selector3.map($0), selector4.map($0), selector5.map($0)) }) } } /// Creator functions. public extension Selector { /** Creates a `Selector` from a `Selector` and a `projector` function. - Parameter selector1: The first `Selector` - Parameter projector: The closure to pass the value from the `Selector` to - Returns: A `Selector` from the given `Selector` and the `projector` function */ static func with(_ selector1: S1, projector: @escaping (S1.Value) -> Value) -> Selector1 { .init(selector1, projector) } /** Creates a `Selector` from a `Selector` and a `KeyPath`. - Parameter selector1: The first `Selector` - Parameter keyPath: The `keyPath` to create the `Selector` from - Parameter keyPath: The `KeyPath` to subscript in the value from the `Selector` - Returns: A `Selector` from the given `Selector` and the `KeyPath` */ static func with(_ selector1: S1, keyPath: KeyPath) -> Selector1 { .init(selector1, keyPath: keyPath) } /** Creates a `Selector` from two `Selector`s and a `projector` function. - Parameter selector1: The first `Selector` - Parameter selector2: The second `Selector` - Parameter projector: The closure to pass the values from the `Selector`s to - Returns: A `Selector` from the given `Selector`s and the `projector` function */ static func with(_ selector1: S1, _ selector2: S2, projector: @escaping (S1.Value, S2.Value) -> Value) -> Selector2 { .init(selector1, selector2, projector) } /** Creates a `Selector` from three `Selector`s and a `projector` function. - Parameter selector1: The first `Selector` - Parameter selector2: The second `Selector` - Parameter selector3: The third `Selector` - Parameter projector: The closure to pass the values from the `Selectors` to - Returns: A `Selector` from the given `Selector`s and the `projector` function */ static func with(_ selector1: S1, _ selector2: S2, _ selector3: S3, projector: @escaping (S1.Value, S2.Value, S3.Value) -> Value) -> Selector3 { .init(selector1, selector2, selector3, projector) } /** Creates a `Selector` from four `Selector`s and a `projector` function. - Parameter selector1: The first `Selector` - Parameter selector2: The second `Selector` - Parameter selector3: The third `Selector` - Parameter selector4: The fourth `Selector` - Parameter projector: The closure to pass the values from the `Selectors` to - Returns: A `Selector` from the given `Selector`s and the `projector` function */ static func with(_ selector1: S1, _ selector2: S2, _ selector3: S3, _ selector4: S4, projector: @escaping (S1.Value, S2.Value, S3.Value, S4.Value) -> Value) -> Selector4 { .init(selector1, selector2, selector3, selector4, projector) } /** Creates a `Selector` from five `Selector`s and a `projector` function. - Parameter selector1: The first `Selector` - Parameter selector2: The second `Selector` - Parameter selector3: The third `Selector` - Parameter selector4: The fourth `Selector` - Parameter selector5: The fifth `Selector` - Parameter projector: The closure to pass the values from the `Selectors` to - Returns: A `Selector` from the given `Selector`s and the `projector` function */ static func with(_ selector1: S1, _ selector2: S2, _ selector3: S3, _ selector4: S4, _ selector5: S5, projector: @escaping (S1.Value, S2.Value, S3.Value, S4.Value, S5.Value) -> Value) -> Selector5 { .init(selector1, selector2, selector3, selector4, selector5, projector) } } /// Memoization support, where the `Selector` remembers the last result to speed up mapping. internal extension Selector { /** Sets the value and the corresponding `stateHash`. - Parameter value: The value to save - Parameter stateHash: The hash of the state the value was selected from */ func setResult(value: Value, forStateHash stateHash: UUID) { result = (stateHash: stateHash, value: value) } /** Selects the `Value` from the `State` based on the subclass's `map` function and saves the result. - If a value is already saved and the saved state hash matches the passed, the saved value is returned. - If a value is already saved but the saved state hash doesn't match the passed a new value is selected and saved along with the passed state hash - Parameter state: The `State` to select from - Parameter stateHash: The hash of the `State` to select from - Returns: The `Value` mapped with the `projector` */ func map(_ state: State, stateHash: UUID) -> Value { if let result = result, result.stateHash == stateHash { return result.value } let value = map(state) setResult(value: value, forStateHash: stateHash) return value } }