Skip to content

Instantly share code, notes, and snippets.

@andersio
Created December 24, 2016 06:40
Show Gist options
  • Select an option

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

Select an option

Save andersio/bdbe823350d2347757af286f8ddf9608 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 {}
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