Manifold is your friend
I find myself writing async code fairly often. Whenever I'm calling an external
service, usually using HTTP, I tend to use the async versions of the libraries
that provide it, like Aleph or HttpKit.
Actually, I like Aleph the most because it integrates nicely with Manifold. That's not a coincidence, they're both written by the same authors. The
usual go-to solution in the Clojure world when it comes to async programming is
of course core.async. I've used it
several times, and since it's so often used, I try to use it whenever the "async
need" arises. However, time and time again I find myself returning to Manifold
.
This probably is because Manifold
is a bit more Clojure-esque. The API is
more idiomatic, they're just functions. Not those weird <!
and >>!
things
core.async
provides. Also, I just find it easier to work with. It allows you
to use deref
where you want, which is a well-known function and also well
supported by other libraries. Another big advantage IMHO is that Manifold
differntiates between a stream and a "deferred". The latter is a single value
that may or may not be "realized" in the future. Usually the functions the
lib provides are a mix of both. For example, when fetching a value from a stream
using take! it
returns another deferred
. And very often a stream is not what you want, but
a deferred
value fits nicely. In core.async
they solve this by returning
channels (as it's called there) that only hold one value. Of course, you could
also do this using Manifold
but you don't have to.
Also, Manifold
is a bit less magic-y, you can pass deferred
s to other functions
as arguments etc., where with go
blocks you have to be more careful. And,
should it be hard to choose, you can connectcore.async
channels to Manifold
streams!
Just take a look at this example:
;; Core async
(require '[clojure.core.async :as ca])
(def c (ca/chan))
;; Write to channel
(ca/go (ca/>! c "test value"))
;; Read it back, ensure it doesn't block forever
(def v (ca/alts!! [c (ca/timeout 1000)]))
;; Now we still have to inspect the result, see which of the channels it returned from
(if (= c (second v))
(println "Got value:" (first v))
(println "Timed out!"))
Whereas for Manifold
, it would look like this:
;; Manifold
(require '[manifold.stream :as ms])
(def s (ms/stream))
;; Write to stream
(ms/put! s "test value") ; Also accepts `nil` btw
;; Read it back, ensure it doesn't block
(def v (deref (ms/take! s) 1000 :timeout))
(if (not= :timeout v)
(println "Got value:" v)
(println "Timed out"))
To me, the Manifold
code just seems more "natural", you can just deref
the result.
Having to use alts!!
just seems clunky to me, with its weird vector return value.
But of course, YMMV.
So should you ever take a look at the MonkeyCI code,
now you know why we use Manifold
and not core.async
.