Skip to content

Instantly share code, notes, and snippets.

@andersio
Last active December 24, 2016 05:14
Show Gist options
  • Select an option

  • Save andersio/fdbee89008a730e3e3a386d7d1fd5b3d to your computer and use it in GitHub Desktop.

Select an option

Save andersio/fdbee89008a730e3e3a386d7d1fd5b3d to your computer and use it in GitHub Desktop.
/*:
> # 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