Hey All, I want to share something I’ve been working on and get some feedback. If you're interested in concurrency 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 clattner "pyramid of doom". Note that the HoneyBee form allows us to effortlessly parallelize the two data fetches.
Many more features are described at HoneyBee.link 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
What do you think? What’s working here? More importantly, what’s not working?
Thanks for your time. Looking forward to your feedback,
Alex