Last active
January 4, 2016 09:40
-
-
Save xNekOIx/c664cbff1556b6c8b189 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
import Foundation | |
import XCPlayground | |
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true | |
enum Effects<A> { | |
typealias Action = A | |
case Nothing | |
case EffectTask(Task<Any, Action>) | |
case Batch([Effects<Action>]) | |
} | |
extension Effects { | |
func append(effects: Effects<Action>) -> Effects<Action> { | |
switch (self, effects) { | |
case (.Nothing, _): return effects | |
case (_, .Nothing): return self | |
case (.EffectTask, .EffectTask): return .Batch([self, effects]) | |
case (.EffectTask, .Batch(let effectsArray)): return .Batch([self] + effectsArray) | |
case (.Batch(let effectsArray), .EffectTask): return .Batch(effectsArray + [effects]) | |
case (.Batch(let effs1), .Batch(let effs2)): return .Batch(effs1 + effs2) | |
default: fatalError("unhandled case") | |
} | |
} | |
func map<U>(f: Action -> U) -> Effects<U> { | |
switch self { | |
case .Nothing: return .Nothing | |
case .EffectTask(let task): return .EffectTask(task.map(f)) | |
case .Batch(let effects): return .Batch(effects.map { $0.map(f) }) | |
} | |
} | |
} | |
enum Result<T, ErrorType> { | |
typealias SuccessType = T | |
case Success(value: SuccessType), Failure(error: ErrorType) | |
func map<U>(f: T -> U) -> Result<U, ErrorType> { | |
switch self { | |
case .Success(let value): return .Success(value: f(value)) | |
case .Failure(let error): return .Failure(error: error) | |
} | |
} | |
} | |
struct NoError: ErrorType {} | |
class Future<T> { | |
var value: T? { | |
willSet(newValue) { | |
assert(value == nil, "Trying to resolve future with value \(newValue) failed\nFuture already resolved with value \(value)") | |
assert(newValue != nil, "Try to evaluate Future with nil failed") | |
} | |
didSet { | |
guard let newVal = value else { return } | |
subscribers.forEach { $0(newVal) } | |
subscribers = [] | |
} | |
} | |
private var subscribers: [T -> ()] = [] | |
func subscribe(subscriber: T -> ()) { | |
switch value { | |
case .Some: subscriber(value!) | |
case .None: subscribers.append(subscriber) | |
} | |
} | |
func map<U>(f: T -> U) -> Future<U> { | |
let nextF = Future<U>() | |
subscribe({ nextF.value = f($0) }) | |
return nextF | |
} | |
} | |
struct Task<T, S> { | |
let execute: T -> Future<S> | |
} | |
extension Task { | |
func task<T, S>(work: T -> S, result: S -> () = { _ in }) -> Task<T, S> { | |
var future = Future<S>() | |
future.subscribe(result) | |
let task = Task<T, S>(execute: { v in | |
future.value = work(v) | |
return future | |
}) | |
return task | |
} | |
} | |
extension Task { | |
func map<U>(f: S -> U) -> Task<T, U> { | |
return Task<T, U>(execute: { v in | |
return self.execute(v).map(f) | |
}) | |
} | |
} | |
struct URLService { | |
let session = NSURLSession.sharedSession() | |
} | |
func HTTPTask(request: NSURLRequest) -> Task<URLService, NSData> { | |
let task = Task<URLService, NSData> { (service) -> Future<NSData> in | |
let future = Future<NSData>() | |
let session = service.session | |
session.dataTaskWithRequest(request, completionHandler: { data, response, error in | |
if let data = data { | |
future.value = data | |
return | |
} | |
}).resume() | |
return future | |
} | |
return task | |
} | |
public class Store<State, Action> { | |
private var state: State | |
private var update: (State, Action) -> (State, Effects<Action>) | |
private var services: [String: Any] = [:] | |
init(initialState: State, effects: Effects<Action> = .Nothing, update: (State, Action) -> (State, Effects<Action>)) { | |
dump(initialState) | |
dump(effects) | |
print("\n===================\n") | |
state = initialState | |
self.update = update | |
dispatchEffects(effects) | |
} | |
private let queue = dispatch_queue_create("com.Store", DISPATCH_QUEUE_SERIAL) | |
public func dispatch(action: Action) { | |
dispatch_async(queue) { | |
let (state, effects) = self.update(self.state, action) | |
self.state = state | |
dump(action) | |
dump(self.state) | |
dump(effects) | |
print("\n===================\n") | |
self.dispatchEffects(effects) | |
} | |
} | |
private func dispatchEffects(effects: Effects<Action>) { | |
dispatch_async(queue) { | |
switch effects { | |
case .Nothing: break | |
case .EffectTask(let task): | |
switch task { | |
case Task<(), Action>: task.map(self.dispatch).execute() | |
default: task.map(self.dispatch).execute(self.service()) | |
} | |
case .Batch(let effects): | |
effects.forEach(self.dispatchEffects) | |
} | |
} | |
} | |
func registerService<T>(service: T) { | |
let key = "\(T.self)" | |
services[key] = service | |
} | |
private func service<T>() -> T { | |
let key = "\(T.self)" | |
return services[key] as! T | |
} | |
} | |
struct Counter { | |
typealias State = UInt | |
enum Action { | |
case Increment, TrueIncrement, RequestIncrement | |
} | |
static func update(state: State, action: Action) -> (State, Effects<Action>) { | |
var state = state | |
var effects: Effects<Action> = .Nothing | |
switch action { | |
case .RequestIncrement: | |
let task = HTTPTask(request: NSURLRequest(URL: NSURL(string: "http://pewpew.com")!)) | |
.map { _ in return Action.TrueIncrement } | |
effects = effects.append(.EffectTask(task)) | |
case .Increment: | |
effects = effects.append(.EffectTask(task(work: { return Action.TrueIncrement }))) | |
case .TrueIncrement: | |
state = state + 1 | |
} | |
return (state, effects) | |
} | |
} | |
struct TwoCounters { | |
struct State { | |
var left: Counter.State | |
var right: Counter.State | |
} | |
enum Action { | |
case UpdateLeft(act: Counter.Action), UpdateRight(act: Counter.Action) | |
} | |
static func update(state: State, action: Action) -> (State, Effects<Action>) { | |
var state = state | |
var effects: Effects<Action> = .Nothing | |
switch action { | |
case .UpdateLeft(let act): | |
let (newLeft, newEffects) = Counter.update(state.left, action: act) | |
state.left = newLeft | |
effects = newEffects.map { Action.UpdateLeft(act: $0) } | |
case .UpdateRight(let act): | |
let (newRight, newEffects) = Counter.update(state.right, action: act) | |
state.right = newRight | |
effects = newEffects.map { Action.UpdateRight(act: $0) } | |
} | |
return (state, effects) | |
} | |
} | |
let store: Store<TwoCounters.State, TwoCounters.Action> = Store(initialState: .init(left: 0, right: 0), update: TwoCounters.update) | |
store.registerService(URLService()) | |
store.dispatch(.UpdateLeft(act: .Increment)) | |
store.dispatch(.UpdateLeft(act: .RequestIncrement)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment