[swift-evolution] Concurrency: Expressive, Easy and Safe.
Alex Lynch
lynch.sft at gmail.com
Sun Jan 7 08:19:57 CST 2018
Hey All,
I want to share something I’ve been working on. I know this isn't strictly
"swift-evolution" content, but I think it relates heavily to the ongoing
conversation around concurrency features in the language (such as
async/await). If you're interested in such topics would you mind taking a
look?
HoneyBee.start { root in
root.setErrorHandler(handleError)
.chain(fetchNewMovieTitle)
.branch { stem in
stem.chain(fetchReviews)
.chain(averageReviews)
+
stem.chain(fetchComments)
.chain(countComments)
}
.setBlockPerformer(DispatchQueue.main)
.chain(updateUI)
}
func handleError(_ error: Error) {}
func fetchNewMovieTitle(completion: (String?, Error?) -> Void) {}
func fetchReviews(for movieTitle: String, completion:
(FailableResult<[String]>) -> Void) {}
func averageReviews(_ reviews: [String]) throws -> Int { return
reviews.count }
func fetchComments(for movieTitle: String, completion: (([String]?, Error?)
-> Void)?) {}
func countComments(_ comments: [String]) -> Int { return comments.count }
func updateUI(withAverageReview: Int, commentsCount: Int) {}
(Yes that compiles. Much thanks to the Language Team.)
HoneyBee makes concurrent programming expressive, easy and safe. The above
recipe lexically matches the flow of execution.
First `fetchNewMovieTitle` is invoked. Then `fetchReviews` and
`fetchComments` are invoked in parallel each receiving the result of
`fetchNewMovieTitle`. `averageReviews` is invoked after `fetchReviews`
finishes and `countComments` is invoked after `fetchComments`. The results
of `averageReviews` and `countComments` are combined and forwarded to
`updateUI`, which is invoked on the main queue.
HoneyBee.start(on: DispatchQueue.main) { root in
root.setErrorHandler(handleError)
.chain(fetchNewMovieTitle)
.chain(fetchReviews)
.map { elem in // parallel map
elem.chain(\.count) // Keypath access
}
.filter { elem in // parallel filtering
elem.chain(isNonTrivial)
}
.reduce { pair in // parallel "pyramid" reduce
pair.chain(+) // operator access
}
.chain(updateUI)
}
func handleError(_ error: Error) {}
func fetchNewMovieTitle(completion: (String?, Error?) -> Void) {}
func fetchReviews(for movieTitle: String, completion:
(FailableResult<[String]>) -> Void) {}
func isNonTrivial(_ int: Int, completion: (Bool) -> Void) {}
func updateUI(withTotalWordsInNonTrivialReviews: Int) {}
The above recipe demonstrates use of parallel map, filter and reduce. The
entire recipe is run on the main queue.
struct Image {}
func processImageData(completionBlock: @escaping (Image?, Error?) -> Void) {
func loadWebResource(named name: String, completion: (Data?, Error?) ->
Void) {}
func decodeImage(dataProfile: Data, image: Data) throws -> Image {
return Image() }
func dewarpAndCleanupImage(_ image: Image, completion: (Image?, Error?)
-> Void) {}
HoneyBee.start { root in
root.setErrorHandler { completionBlock(nil, $0) }
.branch { stem -> Link<(Data,Data)> in
stem.chain(loadWebResource =<< "dataprofile.txt")
+
stem.chain(loadWebResource =<< "imagedata.dat")
}
.chain(decodeImage)
.chain(dewarpAndCleanupImage)
.chain{ completionBlock($0, nil) }
}
}
The above recipe is a translation of the now famous clatner "pyramid of
doom" <https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782>.
Note that the HoneyBee form allows us to effortlessly parallelize the two
data fetches.
HoneyBee has many more features including:
* Total of 34 chain signatures
* Limit to globally control accesses to a resource
* Retry to re-attempt subchains that are known to have transient failures
* Helpful diagnostics when things go wrong
See the full documentation at HoneyBee.link
Thanks for your time. Looking forward to your feedback,
Alex
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20180107/c3b43038/attachment-0001.html>
More information about the swift-evolution
mailing list