Skip to content

Instantly share code, notes, and snippets.

@chriseidhof
Last active August 20, 2018 05:50
Show Gist options
  • Save chriseidhof/0a8b0db1812326b0b0fc4accdbf502fa to your computer and use it in GitHub Desktop.
Save chriseidhof/0a8b0db1812326b0b0fc4accdbf502fa to your computer and use it in GitHub Desktop.
//
// main.swift
// IncrementalWithChanges
//
// Created by Chris Eidhof on 15.08.18.
// Copyright © 2018 objc.io. All rights reserved.
//
import Foundation
protocol Change {
associatedtype Value
func apply(_ : inout Value)
}
extension Change {
func applying(to value: Value) -> Value {
var c = value
apply(&c)
return c
}
}
struct AtomicChange<A>: Change {
let replaceWith: A
func apply(_ value: inout A) {
value = replaceWith
}
}
enum ArrayChange<A>: Change {
case insert(A, at: Int)
case remove(at: Int)
case append(A)
func apply(_ value: inout [A]) {
switch self {
case .append(let x):
value.append(x)
case let .remove(at: i):
value.remove(at: i)
case let .insert(x, at: i):
value.insert(x, at: i)
}
}
}
final class Var<A, C> where C: Change, C.Value == A {
typealias Observer = (_ old: A, _ change: C) -> ()
private var value: A
private var observers: [Observer] = []
init(_ value: A) {
self.value = value
}
func modify(_ c: C) {
let old = value
c.apply(&value)
for o in observers {
o(old, c)
}
}
func observe(initial: (A) -> (), change: @escaping Observer) {
initial(value)
observers.append(change)
}
func transform<B, BChange>(_ f: (A) -> B, _ g: @escaping (A, C) -> BChange) -> Var<B, BChange> where BChange: Change, BChange.Value == B {
let result = Var<B, BChange>(f(value))
observers.append { (y,z) in
let bC = g(y,z)
result.modify(bC)
}
return result
}
}
protocol Filter: Change {
associatedtype Element
func filter(_ condition: (Element) -> Bool, source: Value) -> Self?
}
extension ArrayChange: Filter {
typealias Element = Value.Element
func filter(_ condition: (A) -> Bool, source: [A]) -> ArrayChange<A>? {
return withoutActuallyEscaping(condition) { condition in
let filtered = source.filter(condition)
func offset(for i: Int) -> Int {
let slice = source[0..<i]
return slice.count - slice.lazy.filter(condition).count
}
switch self {
case let .append(x) where condition(x): return self
case let .insert(x, at: i) where condition(x):
return .insert(x, at: i - offset(for: i))
case let .remove(at: i) where condition(source[i]):
return .remove(at: i - offset(for: i))
default: return nil
}
}
}
}
extension Var where C: Filter, A == [C.Element] {
func filter(_ condition: @escaping (C.Element) -> Bool) -> Var<A, C> {
let result = Var<A,C>(value.filter(condition))
observers.append { (source, change) in
if let c = change.filter(condition, source: source) {
result.modify(c)
}
}
return result
}
}
enum Either<A, B> {
case left(A)
case right(B)
}
enum SumChange<A, B, AChange, BChange> where AChange: Change, AChange.Value == A, BChange: Change, BChange.Value == B {
case changeLeft(AChange)
case changeRight(BChange)
case replace(Either<A, B>)
}
enum ProductChange<A, B, AChange, BChange> where AChange: Change, AChange.Value == A, BChange: Change, BChange.Value == B {
case changeFst(AChange)
case changeSnd(BChange)
case changeBoth(AChange, BChange)
}
extension ProductChange: Change {
func apply(_ to: inout (A,B)) {
switch self {
case .changeFst(let c):
c.apply(&to.0)
case .changeSnd(let c):
c.apply(&to.1)
case .changeBoth(let c0, let c1):
c0.apply(&to.0)
c1.apply(&to.1)
}
}
}
extension SumChange: Change {
func apply(_ to: inout Either<A, B>) {
switch self {
case .changeLeft(let l):
guard case var .left(x) = to else { fatalError() }
l.apply(&x)
to = .left(x)
case .changeRight(let r):
guard case var .right(x) = to else { fatalError() }
r.apply(&x)
to = .right(x)
case .replace(let v):
to = v
}
}
}
let arr = Var<[Int], ArrayChange<Int>>([])
arr.filter { $0 % 2 == 0 }.observe(initial: { x in
print("filtered: ", x)
}, change: { (old, c) in
print("filtered change", old, c, c.applying(to: old))
})
arr.modify(.append(1))
arr.modify(.append(2))
arr.modify(.insert(0, at: 2))
arr.modify(.append(3))
arr.modify(.remove(at: 3))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment