Skip to content
This repository has been archived by the owner on Aug 29, 2022. It is now read-only.

Commit

Permalink
Align SuccessValue/FailureValue with Success/Failure in Swift 5
Browse files Browse the repository at this point in the history
  • Loading branch information
zwaldowski committed Jan 14, 2019
1 parent abd6b74 commit 712c024
Show file tree
Hide file tree
Showing 13 changed files with 90 additions and 85 deletions.
29 changes: 17 additions & 12 deletions Sources/Task/ExistentialTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,19 @@ import Deferred.Atomics
///
/// - seealso: `TaskProtocol`
/// - seealso: `Future`
public final class Task<SuccessValue> {
public final class Task<Success> {
@available(*, unavailable, renamed: "Success", message: "Renamed 'Success' to better align with SE-0235, the Swift 5 Result type.")
public typealias SuccessValue = Success

/// A type that represents either a wrapped value or an error, representing the
/// possible return values of a throwing function.
public enum Result {
/// Any error.
public typealias Failure = Error
/// The success value, stored as `Value`.
case success(SuccessValue)
case success(Success)
/// The failure value, stored as any error.
case failure(Error)
case failure(Failure)
}

private let future: Future<Result>
Expand All @@ -215,7 +220,7 @@ public final class Task<SuccessValue> {
}

/// Creates a task whose `upon(_:execute:)` methods use those of `base`.
public init<Wrapped: TaskProtocol>(_ wrapped: Wrapped, progress: Progress) where Wrapped.SuccessValue == SuccessValue {
public init<Wrapped: TaskProtocol>(_ wrapped: Wrapped, progress: Progress) where Wrapped.Success == Success {
self.future = Future<Result>(resultFrom: wrapped)
self.progress = TaskChain(startingWith: wrapped, using: progress).effectiveProgress
}
Expand All @@ -241,7 +246,7 @@ public final class Task<SuccessValue> {
///
/// `cancellation` will be called asynchronously, but not on any specific
/// queue. If you must do work on a specific queue, schedule work on it.
public init<Wrapped: TaskProtocol>(_ wrapped: Wrapped, uponCancel cancellation: (() -> Void)? = nil) where Wrapped.SuccessValue == SuccessValue {
public init<Wrapped: TaskProtocol>(_ wrapped: Wrapped, uponCancel cancellation: (() -> Void)? = nil) where Wrapped.Success == Success {
self.future = Future<Result>(resultFrom: wrapped)
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
self.progress = TaskChain(startingWith: wrapped, uponCancel: cancellation).effectiveProgress
Expand Down Expand Up @@ -304,7 +309,7 @@ extension Task {

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
/// Creates a task whose `upon(_:execute:)` methods use those of `base`.
public convenience init<Wrapped: FutureProtocol>(succeedsFrom wrapped: Wrapped, progress: Progress) where Wrapped.Value == SuccessValue {
public convenience init<Wrapped: FutureProtocol>(succeedsFrom wrapped: Wrapped, progress: Progress) where Wrapped.Value == Success {
let future = Future<Result>(succeedsFrom: wrapped)
self.init(future, progress: progress)
}
Expand All @@ -314,25 +319,25 @@ extension Task {
///
/// `cancellation` will be called asynchronously, but not on any specific
/// queue. If you must do work on a specific queue, schedule work on it.
public convenience init<Wrapped: FutureProtocol>(succeedsFrom wrapped: Wrapped, uponCancel cancellation: (() -> Void)? = nil) where Wrapped.Value == SuccessValue {
public convenience init<Wrapped: FutureProtocol>(succeedsFrom wrapped: Wrapped, uponCancel cancellation: (() -> Void)? = nil) where Wrapped.Value == Success {
let future = Future<Result>(succeedsFrom: wrapped)
self.init(future, uponCancel: cancellation)
}

/// Creates an operation that has already completed with `value`.
public convenience init(success value: @autoclosure() throws -> SuccessValue) {
public convenience init(success value: @autoclosure() throws -> Success) {
let future = Future<Result>(success: value)
self.init(future)
}

/// Creates an operation that has already failed with `error`.
public convenience init(failure error: Error) {
public convenience init(failure error: Failure) {
let future = Future<Result>(failure: error)
self.init(future)
}

/// Creates a task having the same underlying operation as the `other` task.
public convenience init(_ task: Task<SuccessValue>) {
public convenience init(_ task: Task<Success>) {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
self.init(task.future, progress: task.progress)
#else
Expand All @@ -344,7 +349,7 @@ extension Task {
}

/// Create a task that will never complete.
public static var never: Task<SuccessValue> {
public static var never: Task<Success> {
return Task(Future<Result>.never)
}
}
Expand All @@ -367,7 +372,7 @@ extension Task {
}

@available(*, unavailable, renamed: "init(succeedsFrom:progress:)", message: "Replace with 'init(succeedsFrom:progress:)' to disambiguate from a completed Task.")
public convenience init<Wrapped: FutureProtocol>(success wrapped: Wrapped, progress: Progress) where Wrapped.Value == SuccessValue {
public convenience init<Wrapped: FutureProtocol>(success wrapped: Wrapped, progress: Progress) where Wrapped.Value == Success {
fatalError("unavailable initializer cannot be called")
}
#endif
Expand Down
24 changes: 12 additions & 12 deletions Sources/Task/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,26 @@ import Deferred
/// - seealso: `FutureProtocol`
public protocol TaskProtocol: FutureProtocol where Value: Either {
/// A type that represents the success of some asynchronous work.
associatedtype SuccessValue where SuccessValue == Value.Right
associatedtype Success where Success == Value.Right

/// A type that represents the failure of some asynchronous work.
typealias FailureValue = Error
typealias Failure = Error

/// Call some `body` closure if the task successfully completes.
///
/// - parameter executor: A context for handling the `body`.
/// - parameter body: A closure to be invoked when the result is determined.
/// * parameter value: The determined success value.
/// - seealso: `FutureProtocol.upon(_:execute:)`
func uponSuccess(on executor: Executor, execute body: @escaping(_ value: SuccessValue) -> Void)
func uponSuccess(on executor: Executor, execute body: @escaping(_ value: Success) -> Void)

/// Call some `body` closure if the task fails.
///
/// - parameter executor: A context for handling the `body`.
/// - parameter body: A closure to be invoked when the result is determined.
/// * parameter error: The determined failure value.
/// - seealso: `FutureProtocol.upon(_:execute:)`
func uponFailure(on executor: Executor, execute body: @escaping(_ error: FailureValue) -> Void)
func uponFailure(on executor: Executor, execute body: @escaping(_ error: Failure) -> Void)

/// Tests whether the underlying work has been cancelled.
///
Expand Down Expand Up @@ -80,43 +80,43 @@ extension TaskProtocol {
// MARK: - Conditional conformances

extension Future: TaskProtocol where Value: Either {
public typealias SuccessValue = Value.Right
public typealias Success = Value.Right

/// Create a future having the same underlying task as `other`.
public init<Wrapped: TaskProtocol>(resultFrom wrapped: Wrapped) where Wrapped.SuccessValue == SuccessValue {
public init<Wrapped: TaskProtocol>(resultFrom wrapped: Wrapped) where Wrapped.Success == Success {
self = wrapped as? Future<Value> ?? wrapped.every { (result) -> Value in
Value(catching: result.get)
}
}

/// Create a future having the same underlying task as `other`.
public init<Wrapped: FutureProtocol>(succeedsFrom wrapped: Wrapped) where Wrapped.Value == SuccessValue {
public init<Wrapped: FutureProtocol>(succeedsFrom wrapped: Wrapped) where Wrapped.Value == Success {
self = wrapped.every(per: Value.init(right:))
}

/// Creates an future having already filled successfully with `value`.
public init(success value: @autoclosure() throws -> SuccessValue) {
public init(success value: @autoclosure() throws -> Success) {
self.init(value: Value(catching: value))
}

/// Creates an future having already failed with `error`.
public init(failure error: FailureValue) {
public init(failure error: Failure) {
self.init(value: Value(left: error))
}
}

extension Future where Value: Either {
@available(*, unavailable, renamed: "init(resultFrom:)")
public init<Wrapped: TaskProtocol>(task wrapped: Wrapped) where Wrapped.SuccessValue == SuccessValue {
public init<Wrapped: TaskProtocol>(task wrapped: Wrapped) where Wrapped.Success == Success {
fatalError("unavailable initializer cannot be called")
}

@available(*, unavailable, renamed: "init(succeedsFrom:)")
public init<Wrapped: FutureProtocol>(success wrapped: Wrapped) where Wrapped.Value == SuccessValue {
public init<Wrapped: FutureProtocol>(success wrapped: Wrapped) where Wrapped.Value == Success {
fatalError("unavailable initializer cannot be called")
}
}

extension Deferred: TaskProtocol where Value: Either {
public typealias SuccessValue = Value.Right
public typealias Success = Value.Right
}
8 changes: 4 additions & 4 deletions Sources/Task/TaskAndThen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension TaskProtocol {
///
/// Cancelling the resulting task will attempt to cancel both the receiving
/// task and the created task.
public func andThen<NewTask: TaskProtocol>(upon executor: PreferredExecutor, start startNextTask: @escaping(SuccessValue) throws -> NewTask) -> Task<NewTask.SuccessValue> {
public func andThen<NewTask: TaskProtocol>(upon executor: PreferredExecutor, start startNextTask: @escaping(Success) throws -> NewTask) -> Task<NewTask.Success> {
return andThen(upon: executor as Executor, start: startNextTask)
}

Expand All @@ -41,7 +41,7 @@ extension TaskProtocol {
/// `startNextTask` closure. `andThen` submits `startNextTask` to `executor`
/// once the task completes successfully.
/// - see: FutureProtocol.andThen(upon:start:)
public func andThen<NewTask: TaskProtocol>(upon executor: Executor, start startNextTask: @escaping(SuccessValue) throws -> NewTask) -> Task<NewTask.SuccessValue> {
public func andThen<NewTask: TaskProtocol>(upon executor: Executor, start startNextTask: @escaping(Success) throws -> NewTask) -> Task<NewTask.Success> {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let chain = TaskChain(continuingWith: self)
#else
Expand Down Expand Up @@ -71,9 +71,9 @@ extension TaskProtocol {
}

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
return Task<NewTask.SuccessValue>(future, progress: chain.effectiveProgress)
return Task<NewTask.Success>(future, progress: chain.effectiveProgress)
#else
return Task<NewTask.SuccessValue>(future) {
return Task<NewTask.Success>(future) {
cancellationToken.fill(with: ())
}
#endif
Expand Down
4 changes: 2 additions & 2 deletions Sources/Task/TaskAsync.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extension Task {
/// preemptively fail the task.
/// - parameter body: A function body that either calculates and returns the
/// success value for the task or throws to indicate failure.
public static func async(upon queue: DispatchQueue = .any(), flags: DispatchWorkItemFlags = [], onCancel makeError: @autoclosure @escaping() -> Error, execute work: @escaping() throws -> SuccessValue) -> Task {
public static func async(upon queue: DispatchQueue = .any(), flags: DispatchWorkItemFlags = [], onCancel makeError: @autoclosure @escaping() -> Failure, execute work: @escaping() throws -> Success) -> Task {
let deferred = Deferred<Result>()
let semaphore = DispatchSemaphore(value: 1)

Expand All @@ -44,7 +44,7 @@ extension Task {
}

@available(*, unavailable, message: "Replace with 'Task.async(upon:flags:onCancel:)' for clarity.")
public convenience init(upon queue: DispatchQueue = .any(), flags: DispatchWorkItemFlags = [], onCancel produceError: @autoclosure @escaping() -> Error, execute body: @escaping() throws -> SuccessValue) {
public convenience init(upon queue: DispatchQueue = .any(), flags: DispatchWorkItemFlags = [], onCancel produceError: @autoclosure @escaping() -> Error, execute body: @escaping() throws -> Success) {
fatalError()
}
}
4 changes: 2 additions & 2 deletions Sources/Task/TaskChain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ struct TaskChain {
/// Locates or creates the root of a task chain, then increments its
/// total units in preparation for a follow-up operation to be performed.
init<Wrapped: TaskProtocol>(continuingWith wrapped: Wrapped) {
if let task = wrapped as? Task<Wrapped.SuccessValue>, let root = task.progress as? Root {
if let task = wrapped as? Task<Wrapped.Success>, let root = task.progress as? Root {
// If `wrapped` is a Task created normally, reuse the progress root;
// this `map` or `andThen` builds on that progress.
self.root = root
Expand Down Expand Up @@ -153,7 +153,7 @@ struct TaskChain {

/// See `beginAndThen`.
func commitAndThen<Wrapped: TaskProtocol>(with wrapped: Wrapped) {
if let task = wrapped as? Task<Wrapped.SuccessValue>, !(task.progress is Root) {
if let task = wrapped as? Task<Wrapped.Success>, !(task.progress is Root) {
let pendingUnitCount = task.progress.wasGeneratedByTask ? TaskChain.singleUnit : TaskChain.explicitChildUnitCount
root.totalUnitCount += pendingUnitCount - TaskChain.singleUnit
root.adoptChild(task.progress, withPendingUnitCount: pendingUnitCount)
Expand Down
20 changes: 10 additions & 10 deletions Sources/Task/TaskCollections.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ import Dispatch
import Foundation
#endif

private struct AllFilled<SuccessValue>: TaskProtocol {
private struct AllFilled<Success>: TaskProtocol {
let group = DispatchGroup()
let combined = Deferred<Task<SuccessValue>.Result>()
let combined = Deferred<Task<Success>.Result>()
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let progress = Progress()
#else
let cancellations: [() -> Void]
#endif

init<Base: Collection>(_ base: Base, mappingBy transform: @escaping([Base.Element]) -> SuccessValue) where Base.Element: TaskProtocol {
init<Base: Collection>(_ base: Base, mappingBy transform: @escaping([Base.Element]) -> Success) where Base.Element: TaskProtocol {
let array = Array(base)
let queue = DispatchQueue.global(qos: .utility)

Expand All @@ -35,7 +35,7 @@ private struct AllFilled<SuccessValue>: TaskProtocol {

for future in array {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
if let task = future as? Task<Base.Element.SuccessValue> {
if let task = future as? Task<Base.Element.Success> {
progress.monitorChild(task.progress, withPendingUnitCount: 1)
} else {
progress.monitorCompletion(of: future, withPendingUnitCount: 1)
Expand All @@ -59,15 +59,15 @@ private struct AllFilled<SuccessValue>: TaskProtocol {
}
}

func upon(_ executor: Executor, execute body: @escaping(Task<SuccessValue>.Result) -> Void) {
func upon(_ executor: Executor, execute body: @escaping(Task<Success>.Result) -> Void) {
combined.upon(executor, execute: body)
}

func peek() -> Task<SuccessValue>.Result? {
func peek() -> Task<Success>.Result? {
return combined.peek()
}

func wait(until time: DispatchTime) -> Task<SuccessValue>.Result? {
func wait(until time: DispatchTime) -> Task<Success>.Result? {
return combined.wait(until: time)
}

Expand All @@ -86,12 +86,12 @@ extension Collection where Element: TaskProtocol {
/// If any of the contained tasks fail, the returned task will be determined
/// with that failure. Otherwise, once all operations succeed, the returned
/// task will be fulfilled by combining the values.
public func allSucceeded() -> Task<[Element.SuccessValue]> {
public func allSucceeded() -> Task<[Element.Success]> {
guard !isEmpty else {
return Task(success: [])
}

let wrapper = AllFilled(self) { (array) -> [Element.SuccessValue] in
let wrapper = AllFilled(self) { (array) -> [Element.Success] in
// Expect each to be filled but not successful right now.
// swiftlint:disable:next force_unwrapping
return array.compactMap { try? $0.peek()!.get() }
Expand All @@ -105,7 +105,7 @@ extension Collection where Element: TaskProtocol {
}
}

extension Collection where Element: TaskProtocol, Element.SuccessValue == Void {
extension Collection where Element: TaskProtocol, Element.Success == Void {
/// Compose a number of tasks into a single array.
///
/// If any of the contained tasks fail, the returned task will be determined
Expand Down
24 changes: 12 additions & 12 deletions Sources/Task/TaskFallback.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ extension TaskProtocol {
///
/// Cancelling the resulting task will attempt to cancel both the receiving
/// task and the created task.
public func fallback<NewTask: TaskProtocol>(upon executor: PreferredExecutor, to restartTask: @escaping(Error) throws -> NewTask) -> Task<SuccessValue> where NewTask.SuccessValue == SuccessValue {
public func fallback<NewTask: TaskProtocol>(upon executor: PreferredExecutor, to restartTask: @escaping(Failure) throws -> NewTask) -> Task<Success> where NewTask.Success == Success {
return fallback(upon: executor as Executor, to: restartTask)
}

Expand All @@ -40,7 +40,7 @@ extension TaskProtocol {
/// `restartTask` closure. `fallback` submits `restartTask` to `executor`
/// once the task fails.
/// - see: FutureProtocol.andThen(upon:start:)
public func fallback<NewTask: TaskProtocol>(upon executor: Executor, to restartTask: @escaping(Error) throws -> NewTask) -> Task<SuccessValue> where NewTask.SuccessValue == SuccessValue {
public func fallback<NewTask: TaskProtocol>(upon executor: Executor, to restartTask: @escaping(Failure) throws -> NewTask) -> Task<Success> where NewTask.Success == Success {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let chain = TaskChain(continuingWith: self)
#else
Expand Down Expand Up @@ -77,9 +77,9 @@ extension TaskProtocol {
}

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
return Task<SuccessValue>(future, progress: chain.effectiveProgress)
return Task<Success>(future, progress: chain.effectiveProgress)
#else
return Task<SuccessValue>(future) {
return Task<Success>(future) {
cancellationToken.fill(with: ())
}
#endif
Expand All @@ -90,9 +90,9 @@ extension TaskProtocol {
public static func `repeat`(
upon preferredExecutor: PreferredExecutor,
count numberOfAttempts: Int = 3,
continuingIf shouldRetry: @escaping(Error) -> Bool = { _ in return true },
to startTask: @escaping() throws -> Task<SuccessValue>
) -> Task<SuccessValue> {
continuingIf shouldRetry: @escaping(Failure) -> Bool = { _ in return true },
to startTask: @escaping() throws -> Task<Success>
) -> Task<Success> {
return self.repeat(upon: preferredExecutor as Executor, count: numberOfAttempts, continuingIf: shouldRetry, to: startTask)
}

Expand All @@ -112,18 +112,18 @@ extension TaskProtocol {
public static func `repeat`(
upon executor: Executor,
count numberOfAttempts: Int = 3,
continuingIf shouldRetry: @escaping(Error) -> Bool = { _ in return true },
to startTask: @escaping() throws -> Task<SuccessValue>
) -> Task<SuccessValue> {
var lastFailedTask: Task<SuccessValue>
continuingIf shouldRetry: @escaping(Failure) -> Bool = { _ in return true },
to startTask: @escaping() throws -> Task<Success>
) -> Task<Success> {
var lastFailedTask: Task<Success>
do {
lastFailedTask = try startTask()
} catch {
return Task(failure: error)
}

for _ in 0 ..< max(numberOfAttempts, 0) {
lastFailedTask = lastFailedTask.fallback(upon: executor) { (error) -> Task<SuccessValue> in
lastFailedTask = lastFailedTask.fallback(upon: executor) { (error) -> Task<Success> in
guard shouldRetry(error) else {
throw error
}
Expand Down
Loading

0 comments on commit 712c024

Please sign in to comment.