Skip to content

Commit 5db8b30

Browse files
committed
Update README.md
- also commit the `bigComputation` example as a test
1 parent 90c2934 commit 5db8b30

File tree

3 files changed

+66
-17
lines changed

3 files changed

+66
-17
lines changed

README.md

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,87 @@
11
# Deferred [![Build Status](https://travis-ci.org/glessard/deferred.svg?branch=master)](https://travis-ci.org/glessard/deferred)
2-
A lock-free, asynchronous `Result` for Swift 4.2 and up.
2+
A lock-free, asynchronous `Result` for Swift 5 and up.
33

44
`Deferred<T>` allows you to chain computations together. A `Deferred` starts with an indeterminate, *unresolved* value. At some later time it may become *resolved*. Its value is then immutable for as long as that particular `Deferred` instance exists.
55
Until a `Deferred` becomes resolved, computations that depend on it can be saved for future execution using a lock-free, thread-safe algorithm. The results of these computations are also represented by `Deferred` instances. Errors thrown at any point in a `Deferred` context are propagated effortlessly.
66

77
`Deferred` started out as an approximation of OCaml's module [Deferred](https://ocaml.janestreet.com/ocaml-core/111.25.00/doc/async_kernel/#Deferred).
88

99
```
10-
let d = Deferred<Double> {
10+
let d = Deferred<Double, Never> { // holds a `Double`, cannot hold an `Error`
1111
usleep(50000) // or a long calculation
1212
return 1.0
1313
}
1414
print(d.value!) // 1.0, after 50 milliseconds
1515
```
1616

17-
A `Deferred` can schedule code that depends on its result for future execution using the `notify`, `map`, `flatMap` and `apply` methods. Any number of such blocks can depend on the result of a `Deferred`. When an `Error` is thrown, it is propagated to all `Deferred`s that depend on it. A `recover` step can be inserted in a chain of transforms in order to handle potential errors.
17+
A `Deferred` can schedule blocks of code that depend on its result for future execution using the `notify`, `map`, and `flatMap` methods, among others. Any number of such blocks can depend on the result of a `Deferred`. When an `Error` is thrown, it is propagated to all `Deferred`s that depend on it. A `recover` step can be inserted in a chain of transforms in order to handle potential errors.
1818

1919
```
20-
let transform = Deferred { i throws in Double(7*i) } // Deferred<Int throws -> Double>
21-
let operand = Deferred(value: 6).delay(seconds: 0.1) // Deferred<Int>
22-
let result = operand.apply(transform: transform) // Deferred<Double>
23-
print(result.value!) // 42.0
20+
let transform = Deferred<(Int) -> Double, Never> { i throws in Double(7*i) }
21+
let operand = Deferred<Int, Error>(value: 6).delay(seconds: 0.1)
22+
let result = operand.apply(transform: transform) // Deferred<Double, Error>
23+
print(result.value!) // 42.0
2424
```
2525
The `result` property of `Deferred` (and its adjuncts, `value` , `error` and `get()`) will block the current thread until its `Deferred` becomes resolved. The rest of `Deferred` is lock-free.
2626

2727
`Deferred` can run its closure on a specified `DispatchQueue`, or at the requested `DispatchQoS`. The `notify`, `map`, `flatMap`, `apply` and `recover` methods also have these options. By default, closures will be scheduled on a queue created at the current quality-of-service (qos) class.
2828

2929

30+
### Task execution scheduling
31+
32+
Tasks execute when a request exists. When creating a `Deferred` object, allocations are made, but no code is run immediately. When a code that depends on a `Deferred` requests the result, then the task is scheduled for execution. Requests are made by calling the `notify()`, `onValue()`, `onError()`, or `beginExecution()` methods (non-blocking); the `result`, `value` or `error` properties (blocking). Requests propagate up a chain of `Deferred`s.
33+
34+
3035
### Long computations, cancellations and timeouts
3136

32-
Long background computations that support cancellation and timeout can be implemented easily by using the `TBD<T>` subclass of `Deferred<T>`. Its constructor takes a closure whose parameter is a `Resolver`. `Resolver` is associated with a specific instance of `Deferred`; it allows your code to be the data source of that `Deferred` instance, as well as to monitor its state.
37+
Long background computations that support cancellation and timeout can be implemented easily by using the callback-style initializer for `Deferred<T>`. It takes a closure whose parameter is a `Resolver`. `Resolver` is associated with a specific instance of `Deferred`, and allows your code to be the data source of that `Deferred` instance. It also allows the data source to monitor the state of the `Deferred`.
3338

34-
func bigComputation() -> Deferred<Double>
39+
func bigComputation() -> Deferred<Double, Never>
3540
{
36-
return TBD<Double> {
41+
return Deferred {
3742
resolver in
3843
DispatchQueue.global(qos: .utility).async {
3944
var progress = 0
4045
repeat {
46+
// first check that a result is still needed
4147
guard resolver.needsResolution else { return }
42-
Thread.sleep(until: Date() + 0.01) // work hard
48+
// then work towards a partial computation
49+
Thread.sleep(until: Date() + 0.001)
4350
print(".", terminator: "")
4451
progress += 1
4552
} while progress < 20
53+
// we have an answer!
4654
resolver.resolve(value: .pi)
4755
}
4856
}
4957
}
5058

59+
// let the child `Deferred` keep a reference to our big computation
5160
let validated = bigComputation().validate(predicate: { $0 > 3.14159 && $0 < 3.14160 })
5261
let timeout = 0.1
5362
validated.timeout(seconds: timeout, reason: String(timeout))
5463

5564
do {
56-
let pi = try validated.get()
65+
print(validated.state) // still waiting: no request yet
66+
let pi = try validated.get() // make the request and wait for value
5767
print(" ", pi)
5868
}
59-
catch DeferredError.timedOut(let message) {
69+
catch Cancellation.timedOut(let message) {
6070
print()
61-
XCTAssertEqual(message, String(timeout))
71+
assert(message == String(timeout))
6272
}
6373

6474
In the above example, our computation closure works hard to compute the ratio of a circle's circumference to its diameter, then performs a rough validation of the output by comparing it with a known approximation. Note that the `Deferred` object performing the primary computation is not retained directly by this user code. When the timeout is triggered, the computation is correctly abandoned and the object is deallocated. General cancellation can be performed in a similar manner.
6575

66-
`Deferred` is carefully written to support cancellation by leveraging reference counting. In every case, when a new `Deferred` is returned by a function from the package, that returned reference is the only strong reference in existence. This allows cancellation to work properly for entire chains of computations, even if it is applied to the final link of the chain.
76+
`Deferred` is carefully written to support cancellation by leveraging the Swift runtime's reference counting. In every case, when a new `Deferred` is returned by a function from the package, that returned reference is the only strong reference in existence. This allows cancellation to work properly for entire chains of computations, even if it is applied to the final link of the chain, regardless of error types.
6777

6878
### Using `Deferred` in a project
6979

7080
With the swift package manager, add the following to your package manifest's dependencies:
7181

72-
.package(url: "https://github.com/glessard/deferred.git", from: "5.0.0")
82+
.package(url: "https://github.com/glessard/deferred.git", from: "6.0.0")
7383

74-
To integrate in an Xcode project, tools such as `Accio` and `xspm` are good options. The repository contains a project with an example iOS target that is manually assembled. It requires some git submodules to be loaded using the command `git submodule update --init`.
84+
To integrate in an Xcode 10 project, tools such as `Accio` and `xspm` are good options. The repository contains an Xcode 10 project with manually-assembled example iOS target. It requires some git submodules to be loaded using the command `git submodule update --init`.
7585

7686
#### License
7787

Tests/deferredTests/DeferredExamples.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,42 @@ class DeferredExamples: XCTestCase
8282
let result = operand.apply(transform: transform) // Deferred<Double>
8383
print(result.value!) // 42.0
8484
}
85+
86+
func testBigComputation() throws
87+
{
88+
func bigComputation() -> Deferred<Double, Never>
89+
{
90+
return Deferred {
91+
resolver in
92+
DispatchQueue.global(qos: .utility).async {
93+
var progress = 0
94+
repeat {
95+
// first check that a result is still needed
96+
guard resolver.needsResolution else { return }
97+
// then work towards a partial computation
98+
Thread.sleep(until: Date() + 0.001)
99+
print(".", terminator: "")
100+
progress += 1
101+
} while progress < 20
102+
// we have an answer!
103+
resolver.resolve(value: .pi)
104+
}
105+
}
106+
}
107+
108+
// let the child `Deferred` keep a reference to our big computation
109+
let validated = bigComputation().validate(predicate: { $0 > 3.14159 && $0 < 3.14160 })
110+
let timeout = 0.1
111+
validated.timeout(seconds: timeout, reason: String(timeout))
112+
113+
do {
114+
print(validated.state) // still waiting: no request yet
115+
let pi = try validated.get() // make the request and wait for value
116+
print(" ", pi)
117+
}
118+
catch Cancellation.timedOut(let message) {
119+
print()
120+
assert(message == String(timeout))
121+
}
122+
}
85123
}

Tests/deferredTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ extension DeferredExamples {
4242
// `swift test --generate-linuxmain`
4343
// to regenerate.
4444
static let __allTests__DeferredExamples = [
45+
("testBigComputation", testBigComputation),
4546
("testExample", testExample),
4647
("testExample2", testExample2),
4748
("testExample3", testExample3),

0 commit comments

Comments
 (0)