We will get started by taking a look at one of the most used operations in these abstractions: map.
We've been using map for a long time, in order to transform sequences. Thus, instead of creating a new function name for each new abstraction, library designers simply abstract the map operation over its own container type.
Imagine the mess that we would end up in if we had functions such as transform-observable, transform-channel, combine-futures, and so on.
Thankfully, this is not the case. The semantics of map are well understood, to the point that even if a developer hasn't used a specific library before, he will almost always assume that map will apply a function to the value(s) contained within whatever abstraction the library provides.
Let's look at three examples that we have encountered in this book. We will create a new Leiningen project, in which to experiment with the contents of this appendix:
$ lein new library-design
Next, let's add a few dependencies to our project.clj file, as follows:
... :dependencies [[org.clojure/clojure "1.9.0"] [com.leonardoborges/imminent "0.1.1"] [com.netflix.rxjava/rxjava-clojure "0.20.7"] [org.clojure/core.async "0.4.474"] [uncomplicate/fluokitten "0.3.0"]] ...
Don't worry about the last dependency; we'll get to that later on.
Now, start a repl session, so that you can follow along. Type the following code:
$ lein repl
Then, enter the following into your REPL:
(require '[imminent.core :as i] '[rx.lang.clojure.core :as rx] '[clojure.core.async :as async]) (def repl-out *out*) (defn prn-to-repl [& args] (binding [*out* repl-out] (apply prn args))) (-> (i/const-future 31) (i/map #(* % 2)) (i/on-success #(prn-to-repl (str "Value: " %)))) (as-> (rx/return 31) obs (rx/map #(* % 2) obs) (rx/subscribe obs #(prn-to-repl (str "Value: " %)))) (def c (async/chan)) (def mapped-c (async/map< #(* % 2) c)) (async/go (async/>! c 31)) (async/go (prn-to-repl (str "Value: " (async/<! mapped-c)))) "Value: 62" "Value: 62" "Value: 62"
The three examples (using imminent, RxClojure, and core.async, respectively) look remarkably similar. They all follow a simple recipe:
- Put the number 31 inside of their respective abstractions
- Double that number by mapping a function over the abstraction
- Print its result to the REPL
As expected, this will provide the value 62 as the output to the screen, three times.
It would seem that map performs the same abstract steps in all three cases—it applies the provided function, puts the resulting value in a fresh new container, and returns it. We could continue to generalize, but we would just be rediscovering an abstraction that already exists—functors.