Created
December 24, 2016 06:40
-
-
Save andersio/bdbe823350d2347757af286f8ddf9608 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /*: | |
| > # IMPORTANT: To use `ReactiveSwift.playground`, please: | |
| 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveSwift project root directory: | |
| - `git submodule update --init` | |
| **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed | |
| - `carthage checkout --no-use-binaries` | |
| 1. Open `ReactiveSwift.xcworkspace` | |
| 1. Build `Result-Mac` scheme | |
| 1. Build `ReactiveSwift-macOS` scheme | |
| 1. Finally open the `ReactiveSwift.playground` | |
| 1. Choose `View > Show Debug Area` | |
| */ | |
| import Result | |
| import ReactiveSwift | |
| import Foundation | |
| /*: | |
| ## Sandbox | |
| A place where you can build your sand castles 🏖. | |
| */ | |
| protocol NewMutablePropertyProtocol: MutablePropertyProtocol { | |
| func withValue<R>(action: (Value) throws -> R) rethrows -> R | |
| func modify<R>(_ action: (inout Value) throws -> R) rethrows -> R | |
| } | |
| extension MutableProperty: NewMutablePropertyProtocol {} | |
| protocol ActionPropertyProtocol: NewMutablePropertyProtocol { | |
| associatedtype ActionError: Swift.Error | |
| var validations: Signal<Result<(), ActionError>, NoError> { get } | |
| } | |
| final class ActionProperty<Value, ActionError: Swift.Error>: ActionPropertyProtocol { | |
| var actionProperty: ActionProperty<Value, ActionError> { | |
| return self | |
| } | |
| var value: Value { | |
| get { | |
| return cache.value | |
| } | |
| set { | |
| action(newValue) | |
| } | |
| } | |
| var producer: SignalProducer<Value, NoError> { | |
| return cache.producer | |
| } | |
| var signal: Signal<Value, NoError> { | |
| return cache.signal | |
| } | |
| let lifetime: Lifetime | |
| let validations: Signal<Result<(), ActionError>, NoError> | |
| private let action: (Value) -> Void | |
| private let box: ActionPropertyBoxBase<()> | |
| private let cache: Property<Value> | |
| init<M: NewMutablePropertyProtocol>(_ inner: M, transform: @escaping (M.Value) -> Value, enabledIf: @escaping (M.Value) -> Bool, _ body: @escaping (M.Value, Value) -> Result<M.Value, ActionError>) { | |
| let (_validations, validationObserver) = Signal<Result<(), ActionError>, NoError>.pipe() | |
| self.lifetime = inner.lifetime | |
| self.validations = _validations | |
| self.cache = inner.map(transform) | |
| self.box = ActionPropertyBox(inner) | |
| action = { input in | |
| inner.withValue { innerValue in | |
| switch body(innerValue, input) { | |
| case let .success(innerResult): | |
| inner.withValue { _ in | |
| inner.value = innerResult | |
| validationObserver.send(value: .success()) | |
| } | |
| case let .failure(error): | |
| validationObserver.send(value: .failure(error)) | |
| } | |
| } | |
| } | |
| } | |
| convenience init<M: ActionPropertyProtocol>(_ inner: M, transform: @escaping (M.Value) -> Value, enabledIf: @escaping (M.Value) -> Bool, _ body: @escaping (M.Value, Value) -> Result<M.Value, ActionError>) where M.ActionError == ActionError { | |
| self.init(inner, transform: transform, enabledIf: enabledIf, errorTransform: { $0 }, body) | |
| } | |
| init<M: ActionPropertyProtocol>(_ inner: M, transform: @escaping (M.Value) -> Value, enabledIf: @escaping (M.Value) -> Bool, errorTransform: @escaping (M.ActionError) -> ActionError, _ body: @escaping (M.Value, Value) -> Result<M.Value, ActionError>) { | |
| let (_validations, validationObserver) = Signal<Result<(), ActionError>, NoError>.pipe() | |
| self.lifetime = inner.lifetime | |
| self.validations = _validations | |
| self.cache = inner.map(transform) | |
| self.box = ActionPropertyBox(inner) | |
| inner.validations | |
| .map { $0.mapError(errorTransform) } | |
| .observe(validationObserver) | |
| action = { input in | |
| inner.withValue { innerValue in | |
| switch body(innerValue, input) { | |
| case let .success(innerResult): | |
| inner.value = innerResult | |
| case let .failure(error): | |
| validationObserver.send(value: .failure(error)) | |
| } | |
| } | |
| } | |
| } | |
| func withValue<R>(action: (Value) throws -> R) rethrows -> R { | |
| return try box.lock { | |
| return try action(cache.value) | |
| } | |
| } | |
| func modify<R>(_ action: (inout Value) throws -> R) rethrows -> R { | |
| return try box.lock { | |
| return try action(&value) | |
| } | |
| } | |
| } | |
| private class ActionPropertyBoxBase<Value> { | |
| func lock<R>(_ action: (()) throws -> R) rethrows -> R { | |
| fatalError() | |
| } | |
| } | |
| // FIXME: Type parameter to work around type checker weirdness. | |
| private class ActionPropertyBox<M: NewMutablePropertyProtocol>: ActionPropertyBoxBase<()> { | |
| private let base: M | |
| init(_ base: M) { self.base = base } | |
| override func lock<R>(_ action: (()) throws -> R) rethrows -> R { | |
| return try base.withValue { _ in return try action(()) } | |
| } | |
| } | |
| protocol _MutablePropertyOnlyProtocol: NewMutablePropertyProtocol {} | |
| extension MutableProperty: _MutablePropertyOnlyProtocol {} | |
| extension NewMutablePropertyProtocol { | |
| func transform<U>(forward: @escaping (Value) -> U, backward: @escaping (U) -> Value) -> ActionProperty<U, NoError> { | |
| return ActionProperty<U, NoError>(self, transform: forward, enabledIf: { _ in true }) { _, proposedInput in | |
| return .success(backward(proposedInput)) | |
| } | |
| } | |
| } | |
| extension ActionPropertyProtocol { | |
| func transform<U>(forward: @escaping (Value) -> U, backward: @escaping (U) -> Value) -> ActionProperty<U, ActionError> { | |
| return ActionProperty<U, ActionError>(self, transform: forward, enabledIf: { _ in true }) { _, proposedInput in | |
| return .success(backward(proposedInput)) | |
| } | |
| } | |
| } | |
| extension _MutablePropertyOnlyProtocol { | |
| func validating<Error: Swift.Error>(_ body: @escaping (Value) -> Result<(), Error>) -> ActionProperty<Value, Error> { | |
| return ActionProperty(self, transform: { $0 }, enabledIf: { _ in true }) { current, proposedInput in | |
| switch body(proposedInput) { | |
| case .success: | |
| return .success(proposedInput) | |
| case let .failure(error): | |
| return .failure(error) | |
| } | |
| } | |
| } | |
| } | |
| extension ActionPropertyProtocol { | |
| func validating(_ body: @escaping (Value) -> Result<(), ActionError>) -> ActionProperty<Value, ActionError> { | |
| return ActionProperty(self, transform: { $0 }, enabledIf: { _ in true }) { current, proposedInput in | |
| switch body(proposedInput) { | |
| case .success: | |
| return .success(proposedInput) | |
| case let .failure(error): | |
| return .failure(error) | |
| } | |
| } | |
| } | |
| func validating<Error: Swift.Error>(_ body: @escaping (Value) -> Result<(), Error>) -> ActionProperty<Value, ActionPropertyError<Error, ActionError>> { | |
| return ActionProperty(self, transform: { $0 }, enabledIf: { _ in true }, errorTransform: { .inner($0) }) { current, proposedInput in | |
| switch body(proposedInput) { | |
| case .success: | |
| return .success(proposedInput) | |
| case let .failure(error): | |
| return .failure(.outer(error)) | |
| } | |
| } | |
| } | |
| } | |
| enum ActionPropertyError<OuterError: Error, InnerError: Error>: Error { | |
| case outer(OuterError) | |
| case inner(InnerError) | |
| } | |
| enum ValidationError<Value>: Error { | |
| case negative(Value) | |
| } | |
| let t = MutableProperty(0) | |
| let A = t.validating { input -> Result<(), ValidationError<Int>> in | |
| print("A validator") | |
| if (0 ... 5).contains(input) { | |
| return .success() | |
| } else { | |
| return .failure(.negative(input)) | |
| } | |
| } | |
| A.validations.observeValues { validation in | |
| print("level 1 ActionProperty - \(validation); value=\(A.value); rootValue=\(t.value)") | |
| } | |
| A.value = -5 | |
| A.value = 5 | |
| print("----nesting?") | |
| let B = A.validating { input -> Result<(), ValidationError<Int>> in | |
| print("B validator") | |
| if (0 ... 10).contains(input) || (100 ... 110).contains(input) { | |
| return .success() | |
| } else { | |
| return .failure(.negative(input)) | |
| } | |
| } | |
| B.validations.observeValues { validation in | |
| print("level 2 ActionProperty - \(validation); value=\(B.value); rootValue=\(t.value)") | |
| } | |
| B.value = -5 | |
| B.value = 5 | |
| B.value = 10 | |
| B.value = 100 | |
| print("----3 level?") | |
| let C = B.validating { input -> Result<(), ValidationError<Int>> in | |
| print("C validator") | |
| if (0 ... 15).contains(input) || (200 ... 210).contains(input) { | |
| return .success() | |
| } else { | |
| return .failure(.negative(input)) | |
| } | |
| } | |
| C.validations.observeValues { validation in | |
| print("level 3 ActionProperty - \(validation); value=\(C.value); rootValue=\(t.value)") | |
| } | |
| C.value = -5 | |
| C.value = 5 | |
| C.value = 15 | |
| C.value = 100 | |
| C.value = 200 | |
| print("----4 level with a different error type??") | |
| let D = C.validating { input -> Result<(), ValidationError<String>> in | |
| print("D validator") | |
| if (0 ... 20).contains(input) || (300 ... 310).contains(input) { | |
| return .success() | |
| } else { | |
| return .failure(.negative(String(input))) | |
| } | |
| } | |
| D.validations.observeValues { validation in | |
| print("level 4 ActionProperty - \(validation); value=\(D.value); rootValue=\(t.value)") | |
| } | |
| D.value = -5 | |
| D.value = 5 | |
| D.value = 20 | |
| D.value = 100 | |
| D.value = 200 | |
| D.value = 300 | |
| print("----5 level with a transform") | |
| let E = D.transform(forward: { String("\($0)") }, backward: { Int($0)! }) | |
| E.validations.observeValues { validation in | |
| print("level 5 ActionProperty - \(validation); value=\(D.value); rootValue=\(t.value)") | |
| } | |
| E.value = "-5" | |
| E.value = "5" | |
| E.value = "20" | |
| E.value = "100" | |
| E.value = "200" | |
| E.value = "300" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment