/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Deferred // Haskell, baby. // Monadic bind/flatMap operator for Deferred. precedencegroup MonadicBindPrecedence { associativity: left higherThan: MonadicDoPrecedence lowerThan: BitwiseShiftPrecedence } precedencegroup MonadicDoPrecedence { associativity: left higherThan: MultiplicationPrecedence } infix operator >>== : MonadicBindPrecedence infix operator >>> : MonadicDoPrecedence @discardableResult public func >>== (x: Deferred>, f: @escaping (T) -> Deferred>) -> Deferred> { return chainDeferred(x, f: f) } // A termination case. public func >>== (x: Deferred>, f: @escaping (T) -> Void) { return x.upon { result in if let v = result.successValue { f(v) } } } // Monadic `do` for Deferred. @discardableResult public func >>> (x: Deferred>, f: @escaping () -> Deferred>) -> Deferred> { return x.bind { res in if res.isSuccess { return f() } return deferMaybe(res.failureValue!) } } // Another termination case. public func >>> (x: Deferred>, f: @escaping () -> Void) { return x.upon { res in if res.isSuccess { f() } } } /** * Returns a thunk that return a Deferred that resolves to the provided value. */ public func always(_ t: T) -> () -> Deferred> { return { deferMaybe(t) } } public func deferMaybe(_ s: T) -> Deferred> { return Deferred(value: Maybe(success: s)) } public func deferMaybe(_ e: MaybeErrorType) -> Deferred> { return Deferred(value: Maybe(failure: e)) } public typealias Success = Deferred> @discardableResult public func succeed() -> Success { return deferMaybe(()) } /** * Return a single Deferred that represents the sequential chaining * of f over the provided items. */ public func walk(_ items: [T], f: @escaping (T) -> Success) -> Success { return items.reduce(succeed()) { success, item -> Success in success >>> { f(item) } } } /** * Like `all`, but thanks to its taking thunks as input, each result is * generated in strict sequence. Fails immediately if any result is failure. */ public func accumulate(_ thunks: [() -> Deferred>]) -> Deferred> { if thunks.isEmpty { return deferMaybe([]) } let combined = Deferred>() var results: [T] = [] results.reserveCapacity(thunks.count) var onValue: ((T) -> Void)! var onResult: ((Maybe) -> Void)! // onValue and onResult both hold references to each other niling them out before exiting breaks a reference cycle // We also cannot use unowned here because the thunks are not class types. onValue = { t in results.append(t) if results.count == thunks.count { onResult = nil combined.fill(Maybe(success: results)) } else { thunks[results.count]().upon(onResult) } } onResult = { r in if r.isFailure { onValue = nil combined.fill(Maybe(failure: r.failureValue!)) return } onValue(r.successValue!) } thunks[0]().upon(onResult) return combined } /** * Take a function and turn it into a side-effect that can appear * in a chain of async operations without producing its own value. */ public func effect(_ f: @escaping (T) -> U) -> (T) -> Deferred> { return { t in _ = f(t) return deferMaybe(t) } } /** * Return a single Deferred that represents the sequential chaining of * f over the provided items, with the return value chained through. */ public func walk(_ items: S, start: Deferred>, f: @escaping (T, U) -> Deferred>) -> Deferred> where S.Iterator.Element == T { let fs = items.map { item in return { val in f(item, val) } } return fs.reduce(start, >>==) } /** * Like `all`, but doesn't accrue individual values. */ extension Array where Element: Success { public func allSucceed() -> Success { return all(self).bind { results -> Success in if let failure = results.find({ $0.isFailure }) { return deferMaybe(failure.failureValue!) } return succeed() } } } public func chainDeferred(_ a: Deferred>, f: @escaping (T) -> Deferred>) -> Deferred> { return a.bind { res in if let v = res.successValue { return f(v) } return Deferred(value: Maybe(failure: res.failureValue!)) } } public func chainResult(_ a: Deferred>, f: @escaping (T) -> Maybe) -> Deferred> { return a.map { res in if let v = res.successValue { return f(v) } return Maybe(failure: res.failureValue!) } } public func chain(_ a: Deferred>, f: @escaping (T) -> U) -> Deferred> { return chainResult(a, f: { Maybe(success: f($0)) }) } /// Defer-ifies a block to an async dispatch queue. public func deferDispatchAsync(_ queue: DispatchQueue, f: @escaping () -> Deferred>) -> Deferred> { let deferred = Deferred>() queue.async(execute: { f().upon { result in deferred.fill(result) } }) return deferred }