官术网_书友最值得收藏!

Transducers

Before we finish up with our core.async portion of this book, we should mention what came up in Clojure 1.7, as well as how this affects core.async.

One of the big changes in this release was the introduction of transducers. We will not cover the nuts and bolts of it here, but rather focus on what it means at a high-level with examples using both Clojure sequences and core.async channels.

If you would like to know more, I recommend Carin Meier's Green Eggs and Transducers blog post[4]. It's a great place to start.

Additionally, the official Clojure documentation site on the subject is another useful resource[5].

Let's get started by creating a new Leiningen project:

$ lein new core-async-transducers  

Now, open your project.clj file and make sure you have the right dependencies:

... 
  :dependencies [[org.clojure/clojure "1.9.0"] 
                 [org.clojure/core.async "0.4.474"]] 
... 

Next, fire up a REPL session in the project root and require core.async, which we will be using shortly:

    $ lein repl
    user> (require '[clojure.core.async :refer [go chan map< filter< into >! <! go-loop close! pipe] :as async])  

We will start with a familiar example:

    (->> (range 10)
         (map inc)           ;; creates a new sequence
         (filter even?)      ;; creates a new sequence
         (prn "result is "))
    ;; "result is " (2 4 6 8 10)  

The preceding snippet is straightforward and highlights an interesting property of what happens when we apply combinators to Clojure sequences: each combinator creates an intermediate sequence.

In the previous example, we ended up with three in total: the one created by range, the one created by map, and finally, the one created by filter. Most of the time, this won't really be an issue, but for large sequences, this means a lot of unnecessary allocation.

Starting in Clojure 1.7, the previous example can be written like so:

    (def xform
      (comp (map inc)
            (filter even?)))  ;; no intermediate sequence created
    
    (->> (range 10)
         (sequence xform)
         (prn "result is "))
    ;; "result is " (2 4 6 8 10)  

The Clojure documentation describes transducers as composable algorithmic transformations. Let's see why that is.

In the new version, a whole range of the core sequence combinators, such as map and filter, have gained extra arity: if you don't pass it a collection, it instead returns a transducer.

In the previous example, (map inc) returns a transducer that knows how to apply the inc function to elements of a sequence. Similarly, (filter even?) returns a transducer that will eventually filter elements of a sequence. Neither of them do anything yet—they simply return functions.

This is interesting because transducers are composable. We can build larger and more complex transducers by using simple function composition:

    (def xform
      (comp (map inc)
            (filter even?)))  

Once we have our transducer ready, we can apply it to a collection in a few different ways. For this example, we chose sequence, as it will return a lazy sequence of the applications of the given transducer to the input sequence:

    (->> (range 10)
         (sequence xform)
         (prn "result is "))
    ;; "result is " (2 4 6 8 10)  

As we highlighted previously, this code does not create intermediate sequences; transducers extract the very core of the algorithmic transformation at hand and abstract it away from having to deal with sequences directly.

主站蜘蛛池模板: 汉中市| 会泽县| 西贡区| 浮梁县| 夏邑县| 阆中市| 石首市| 泰和县| 石狮市| 临汾市| 金平| 大关县| 正安县| 锡林浩特市| 霍林郭勒市| 鄂托克旗| 和田市| 南漳县| 礼泉县| 广水市| 梁平县| 华池县| 三门县| 新兴县| 英吉沙县| 繁峙县| 定陶县| 荣昌县| 高淳县| 黔西| 武平县| 广宁县| 桑植县| 犍为县| 荆门市| 赣榆县| 寿光市| 桐梓县| 嘉禾县| 萝北县| 衢州市|