Last active
December 24, 2016 05:14
-
-
Save andersio/fdbee89008a730e3e3a386d7d1fd5b3d 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 {} | |
| final class Validating<P: NewMutablePropertyProtocol, Error: Swift.Error>: NewMutablePropertyProtocol { | |
| let inner: P | |
| var value: P.Value { | |
| get { | |
| return inner.value | |
| } | |
| set { | |
| let validation = validator(newValue) | |
| switch validation { | |
| case .success: | |
| inner.value = newValue | |
| case .failure: | |
| break | |
| } | |
| validationObserver.send(value: validation) | |
| } | |
| } | |
| var producer: SignalProducer<P.Value, NoError> { | |
| return inner.producer | |
| } | |
| var signal: Signal<P.Value, NoError> { | |
| return inner.signal | |
| } | |
| var lifetime: Lifetime { | |
| return inner.lifetime | |
| } | |
| let (validations, validationObserver) = Signal<Result<(), Error>, NoError>.pipe() | |
| let validator: (P.Value) -> Result<(), Error> | |
| init(_ inner: P, _ body: @escaping (P.Value) -> Result<(), Error>) { | |
| self.inner = inner | |
| self.validator = body | |
| } | |
| func withValue<R>(action: (P.Value) throws -> R) rethrows -> R { | |
| return try inner.withValue(action: action) | |
| } | |
| func modify<R>(_ action: (inout P.Value) throws -> R) rethrows -> R { | |
| return try inner.modify(action) | |
| } | |
| } | |
| protocol ActionPropertyProtocol: NewMutablePropertyProtocol { | |
| associatedtype ActionError: Swift.Error | |
| var actionProperty: ActionProperty<Value, ActionError> { 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.apply(newValue).start() | |
| } | |
| } | |
| var producer: SignalProducer<Value, NoError> { | |
| return cache.producer | |
| } | |
| var signal: Signal<Value, NoError> { | |
| return action.values | |
| } | |
| let lifetime: Lifetime | |
| var errors: Signal<ActionError, NoError> { | |
| return action.errors | |
| } | |
| var validations: Signal<Result<(), ActionError>, NoError> { | |
| return Signal.merge(signal.map { _ in Result<(), ActionError>.success(()) }, | |
| errors.map { Result<(), ActionError>.failure($0) }) | |
| } | |
| private let action: Action<Value, Value, ActionError> | |
| private let cache: Property<Value> | |
| private let box: ActionPropertyBoxBase<()> | |
| init<M: NewMutablePropertyProtocol>(_ inner: M, initial: (M.Value) -> Value, enabledIf: @escaping (M.Value) -> Bool, _ body: @escaping (M.Value, Value) -> Result<(M.Value, Value), ActionError>) { | |
| self.box = ActionPropertyBox(inner) | |
| self.lifetime = inner.lifetime | |
| self.action = Action(state: inner, enabledIf: enabledIf) { current, input in | |
| return SignalProducer { observer, disposable in | |
| switch body(current, input) { | |
| case let .success((innerResult, outerResult)): | |
| inner.withValue { _ in | |
| inner.value = innerResult | |
| observer.send(value: outerResult) | |
| observer.sendCompleted() | |
| } | |
| case let .failure(error): | |
| observer.send(error: error) | |
| } | |
| } | |
| } | |
| self.cache = Property(initial: initial(inner.value), then: action.values) | |
| } | |
| convenience init<M: ActionPropertyProtocol>(_ inner: M, initial: (M.Value) -> Value, enabledIf: @escaping (M.Value) -> Bool, _ body: @escaping (M.Value, Value) -> Result<(M.Value, Value), ActionError>) where M.ActionError == ActionError { | |
| self.init(inner, initial: initial, enabledIf: enabledIf, errorTransform: { $0 }, body) | |
| } | |
| init<M: ActionPropertyProtocol>(_ inner: M, initial: (M.Value) -> Value, enabledIf: @escaping (M.Value) -> Bool, errorTransform: @escaping (M.ActionError) -> ActionError, _ body: @escaping (M.Value, Value) -> Result<(M.Value, Value), ActionError>) { | |
| self.box = ActionPropertyBox(inner) | |
| self.lifetime = inner.lifetime | |
| self.action = Action(state: inner, enabledIf: enabledIf) { current, input in | |
| return SignalProducer { observer, disposable in | |
| switch body(current, input) { | |
| case let .success((innerResult, outerResult)): | |
| inner.actionProperty.action.apply(innerResult).start { event in | |
| switch event { | |
| case .value: | |
| observer.send(value: outerResult) | |
| observer.sendCompleted() | |
| case .completed, .interrupted: | |
| break | |
| case let .failed(error): | |
| switch error { | |
| case .disabled: | |
| break | |
| case let .producerFailed(error): | |
| observer.send(error: errorTransform(error)) | |
| } | |
| } | |
| } | |
| case let .failure(error): | |
| observer.send(error: error) | |
| } | |
| } | |
| } | |
| self.cache = Property(initial: initial(inner.value), then: action.values) | |
| } | |
| 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.modify { _ in return try action(()) } | |
| } | |
| } | |
| protocol _MutablePropertyOnlyProtocol: NewMutablePropertyProtocol {} | |
| extension MutableProperty: _MutablePropertyOnlyProtocol {} | |
| extension _MutablePropertyOnlyProtocol { | |
| func validating<Error: Swift.Error>(_ body: @escaping (Value) -> Result<(), Error>) -> ActionProperty<Value, Error> { | |
| return ActionProperty(self, initial: { $0 }, enabledIf: { _ in true }) { current, proposedInput in | |
| switch body(proposedInput) { | |
| case .success: | |
| return .success((proposedInput, proposedInput)) | |
| case let .failure(error): | |
| return .failure(error) | |
| } | |
| } | |
| } | |
| } | |
| extension ActionPropertyProtocol { | |
| func validating(_ body: @escaping (Value) -> Result<(), ActionError>) -> ActionProperty<Value, ActionError> { | |
| return ActionProperty(self, initial: { $0 }, enabledIf: { _ in true }) { current, proposedInput in | |
| switch body(proposedInput) { | |
| case .success: | |
| return .success((proposedInput, 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, initial: { $0 }, enabledIf: { _ in true }, errorTransform: { .inner($0) }) { current, proposedInput in | |
| switch body(proposedInput) { | |
| case .success: | |
| return .success((proposedInput, 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 input < 0 { | |
| return .failure(.negative(input)) | |
| } else { | |
| return .success() | |
| } | |
| } | |
| A.validations.observeValues { validation in | |
| print("level 1 ActionProperty - \(validation); value=\(A.value); rootValue=\(t.value)") | |
| } | |
| A.value = 1 | |
| A.value = 2 | |
| A.value = -1 | |
| A.value = -2 | |
| print("----nesting?") | |
| let B = A.validating { input -> Result<(), ValidationError<Int>> in | |
| print("B validator") | |
| if input != 12345 && input != -12345 { | |
| return .failure(.negative(input)) | |
| } else { | |
| return .success() | |
| } | |
| } | |
| B.validations.observeValues { validation in | |
| print("level 2 ActionProperty - \(validation); value=\(B.value); rootValue=\(t.value)") | |
| } | |
| B.value = 1 | |
| B.value = 2 | |
| B.value = -1 | |
| B.value = 12345 | |
| B.value = -12345 | |
| print("----3 level?") | |
| let C = B.validating { input -> Result<(), ValidationError<Int>> in | |
| print("C validator") | |
| if input != 12345 && input != -12345 { | |
| return .failure(.negative(input)) | |
| } else { | |
| return .success() | |
| } | |
| } | |
| C.validations.observeValues { validation in | |
| print("level 3 ActionProperty - \(validation); value=\(C.value); rootValue=\(t.value)") | |
| } | |
| C.value = 1 | |
| C.value = 2 | |
| C.value = -1 | |
| C.value = 12345 | |
| C.value = -12345 | |
| print("----4 level with a different error type??") | |
| let D = C.validating { input -> Result<(), ValidationError<String>> in | |
| print("D validator") | |
| if input != 12345 && input != -12345 { | |
| return .failure(.negative(String(input))) | |
| } else { | |
| return .success() | |
| } | |
| } | |
| D.validations.observeValues { validation in | |
| print("level 4 ActionProperty - \(validation); value=\(D.value); rootValue=\(t.value)") | |
| } | |
| D.value = 1 | |
| D.value = 2 | |
| D.value = -1 | |
| D.value = 12345 | |
| D.value = -12345 | |
| print("----") | |
| let t2 = MutableProperty(0) | |
| let v2 = Validating(t) { input -> Result<(), ValidationError<Int>> in | |
| if input < 0 { | |
| return .failure(.negative(input)) | |
| } else { | |
| return .success() | |
| } | |
| } | |
| v2.validations.observeValues { validation in | |
| print("action - \(validation); value=\(v2.value)") | |
| } | |
| v2.value = 1 | |
| v2.value = 2 | |
| v2.value = -1 | |
| v2.value = -2 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment