{"id":32183930,"url":"https://github.com/clojurewerkz/meltdown","last_synced_at":"2025-10-21T23:17:11.110Z","repository":{"id":62431635,"uuid":"10126998","full_name":"clojurewerkz/meltdown","owner":"clojurewerkz","description":"Clojure interface to Reactor, an event-driven programming and stream processing toolkit for the JVM","archived":false,"fork":false,"pushed_at":"2016-01-02T01:52:21.000Z","size":263,"stargazers_count":210,"open_issues_count":1,"forks_count":12,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-09-20T06:34:50.613Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clojurewerkz.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-05-17T16:00:28.000Z","updated_at":"2025-07-20T11:43:15.000Z","dependencies_parsed_at":"2022-11-01T20:46:36.094Z","dependency_job_id":null,"html_url":"https://github.com/clojurewerkz/meltdown","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/clojurewerkz/meltdown","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Fmeltdown","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Fmeltdown/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Fmeltdown/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Fmeltdown/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clojurewerkz","download_url":"https://codeload.github.com/clojurewerkz/meltdown/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojurewerkz%2Fmeltdown/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280348593,"owners_count":26315488,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-21T23:17:09.906Z","updated_at":"2025-10-21T23:17:11.101Z","avatar_url":"https://github.com/clojurewerkz.png","language":"Clojure","funding_links":[],"categories":["\u003ca name=\"Clojure\"\u003e\u003c/a\u003eClojure"],"sub_categories":[],"readme":"# Meltdown, a Clojure Interface to Reactor\n\nMeltdown is a Clojure interface to [Reactor](http://projectreactor.io/), an asynchronous\nprogramming, event passing and stream processing [toolkit for the JVM](http://spring.io/blog/2013/11/12/it-can-t-just-be-big-data-it-has-to-be-fast-data-reactor-1-0-goes-ga).\n\nIt follows the path of\n[Romulan](https://github.com/clojurewerkz/romulan), an old\nClojureWerkz project on top of [LMAX Disruptor](http://lmax-exchange.github.io/disruptor/) that's\nbeen abandoned.\n\n\n## Project Goals\n\n * Provide a convenient, reasonably idiomatic Clojure API for Reactor\n * Not introduce a lot of overhead\n * Be well documented\n * Be well tested\n\nIn addition to providing a core Reactor API for Clojure, Meltdown provides a DSL\nfor stream processing.\n\n\n## Project Maturity\n\nMeltdown is **young**, although the API hasn't changed\nin a while. However, as Reactor itself is young, breaking\nAPI changes are not out of the question.\n\n\n\n## Artifacts\n\nMeltdown artifacts are\n[released to Clojars](https://clojars.org/clojurewerkz/meltdown). If you\nare using Maven, add the following repository definition to your `pom.xml`:\n\n``` xml\n\u003crepository\u003e\n  \u003cid\u003eclojars.org\u003c/id\u003e\n  \u003curl\u003ehttp://clojars.org/repo\u003c/url\u003e\n\u003c/repository\u003e\n```\n\n### The Most Recent Release\n\nWith Leiningen:\n\n```clj\n[clojurewerkz/meltdown \"1.1.0\"]\n```\n\nWith Maven:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eclojurewerkz\u003c/groupId\u003e\n  \u003cartifactId\u003emeltdown\u003c/artifactId\u003e\n  \u003cversion\u003e1.1.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Community\n\nMeltdown uses\n[Reactor mailing list](https://groups.google.com/group/reactor-framework/). Feel\nfree to join it and ask any questions you may have.\n\n\nTo subscribe for announcements of releases, important changes and so on,\nplease follow [@ClojureWerkz](https://twitter.com/#!/clojurewerkz) on\nTwitter.\n\n\n## Documentation\n\n### Basic concepts\n\n`Reactor` is an event-driven programming toolkit for the JVM which\noffers multiple features. At its core, however, it is a message\npassing library, that's used to tie event publishers and consumers\ntogether. Handlers can be attached to and detached from a reactor\ndynamically. When handler is attached to reactor, selector is\nused. Selectors determine what handlers will be invoked.\n\nTo start using Meltdown, first define a reactor\nusing `clojurewerkz.meltdown.reactor/create`:\n\n```clj\n(require '[clojurewerkz.meltdown.reactor :as mr])\n\n(let [reactor (mr/create)]\n  ;; your code\n  )\n```\n\nYou can subscribe to events triggered within reactor by using\n`clojurewerkz.meltdown.reactor/on`:\n\n```clj\n(require '[clojurewerkz.meltdown.reactor :as mr]\n          [clojurewerkz.meltdown.selectors :refer [$]])\n\n(mr/on reactor ($ \"key\") (fn [event]\n                           (comment \"Do something here\")))\n```\n\n`($ \"key\")` here is a **selector**. In essence, that means that every time\nreactor receives event dispatched with the key `\"key\"`, it will call your\nhandler.\n\nIn order to push events into reactor, use `clojurewerkz.meltdown.reactor/notify`:\n\n```clj\n(require '[clojurewerkz.meltdown.reactor :as mr])\n\n;; you can pass any data structures/objects over\n;; reactors\n(mr/notify reactor \"key\" {:my \"payload\"})\n```\n\n### Selectors\n\nSelectors determine which consumers will be invoked for an event.\n\nThere are multiple types of selectors supported by Reactor. Meltdown\nprimarily focuses on two types: exact match and regular\nexpressions. \n\n#### Exact Match Selector\n\nThe exact match should be used for cases when you want\nhandler to respond to a single key:\n\n```clj\n(require '[clojurewerkz.meltdown.reactor :as mr :refer [$]])\n\n(mr/on reactor ($ \"key\") (fn [event] (do-one-thing event)))\n(mr/on reactor ($ \"key\") (fn [event] (do-other-thing event)))\n(mr/on reactor ($ \"key\") (fn [event] (do-something-else event)))\n\n(mr/notify reactor \"key\" {:my :payload}) ;; will fire all three handlers\n(mr/notify reactor \"other\" {:other :payload}) ;; will fire none\n```\n\nIn the example above all three handlers will receive the event.\n\n#### Regular Expression Selector\n\nRegular expression selectors are used whenever you want to match one or\nmany event keys based on a pattern, for example:\n\n```clj\n(require '[clojurewerkz.meltdown.reactor :as mr :refer [R]])\n\n(mr/on reactor (R \"USA.*\") (fn [event] (usa-handler event)))\n(mr/on reactor (R \"Europe.*\") (fn [event] (europe-handler event)))\n(mr/on reactor (R \"Europe.Sw*\") (fn [event] (sw-handler event)))\n\n(mr/notify reactor \"USA.EastCoast\" {:teh :payload}) ;; will fire USA.*\n(mr/notify reactor \"Europe.Germany\" {:das :payload}) ;; will fire Europe.*\n(mr/notify reactor \"Europe.Sweden\" {:das :payload}) ;; will Europe.Sw* and Europe.* handlers\n(mr/notify reactor \"Europe.Switzerland\" {:das :payload}) ;; will Europe.Sw* and Europe.* handlers\n(mr/notify reactor \"Asia.China\" {:teh :payload}) ;; will fire none\n```\n\n#### Match All Selector\n\nMatch all selector unconditionally matches all keys:\n\n```clj\n(require '[clojurewerkz.meltdown.reactor :as mr :refer [match-all]])\n\n;; will match all keys\n(mr/on reactor (match-all) (fn) [event] ...)\n```\n\n#### Custom Selectors\n\nTo produce a custom selector, reify `reactor.event.selector.Selector`\nwhich looks like this:\n\n``` java\npackage reactor.event.selector;\n\n/**\n * A {@literal Selector} is a wrapper around an arbitrary object.\n *\n * @author Jon Brisbin\n * @author Stephane Maldini\n * @author Andy Wilkinson\n */\npublic interface Selector  {\n\t/**\n\t * Get the object being used for comparisons and equals checks.\n\t *\n\t * @return The internal object.\n\t */\n\tObject getObject();\n\n\t/**\n\t * Indicates whether this Selector matches the {@code key}.\n\t *\n\t * @param key The key to match\n\t *\n\t * @return {@code true} if there's a match, otherwise {@code false}.\n\t */\n\tboolean matches(Object key);\n\n\t/**\n\t * Return a component that can resolve headers from a key\n\t *\n\t * @return A {@link HeaderResolver} applicable to this {@link Selector} type.\n\t */\n\tHeaderResolver getHeaderResolver();\n\n}\n```\n\n\n### Routing Strategies\n\nWhenever you have more than a single handler registered for a selector, you\ncan define a routing strategy:\n\n * `:first` routing strategy will take a first handler whose selector matches\n   the key\n * `:broadcast` will dispatch event to every handler whose selector\n   matches the key\n * `:round-robin` will pick handlers on the round robin basis. For example, if there're\n   three handlers, first event will get to first, second to second,\n   third to third, fourth to first again, and so on.\n\nYou can chose a routing strategy that makes most sense for your\napplication. `:first` is usually used when there should be a guarantee\nthat a single, first-assigned handler should take care of\nevent. `:broadcast` makes sense when all handlers should get an event\nsimultaneously, and perform different actions. And `:round-robin` would\nmake sense for things like load-balancing, whenever you would like to\nkeep all workers equally busy, therefore giving the one that just\nreceived an event a chance to take care of it before it gets the next one.\n\nIn order to chose a routing strategy, pass one of the mentioned values\nto `create` function, for example:\n\n```clj\n(mr/create :event-routing-strategy :broadcast)\n```\n\n### Dispatchers\n\nThere are several types of dispatchers, providing you a toolkit for both\nthreadpool-style long-running execution to high-throughput task\ndispatching.\n\n  * default one, synchronous dispatcher, implementation that dispatches\n    events using the calling thread.\n  * `:event-loop` dispatcher implementation that dispatches events using\n    the single dedicated thread. Together with default synchronous dispatcher,\n    very useful in development mode.\n  * `:thread-pool` dispatcher implementation that uses\n    `ThreadPoolExecution` with an unbounded queue to dispatch\n    events. Works best for long-running tasks.\n  * `:ring-buffer` dispatcher implementation that uses LMAX Disruptor\n    RingBuffer to queue tasks to execute. Known to be most\n    high-throughput implementation.\n\n\nIn order to create a reactor backed by the dispatched of your\npreference, pass `:dispatcher-type` to `create` function, for example:\n\n```clj\n(mr/create :dispatcher-type :ring-buffer)\n```\n\n### Request/response Pattern\n\nIt is possible to receive get a callback from the callee. In order to\nimplement request-response with callback, use `send-event`\n`receive-event` pair of methods.\n\n`receive-event` is used instead of `notify` for performance\nreasons. It's an expensive operation to check for `:respond-to` field\nfor each event. Result of the handler execution will be passed back to\nthe caller.\n\nFor example, if you'd like to send an event with `hello-key` key, execute a\ncallback function whenever response is received, you can do it that way:\n\n```clj\n;; Result of handler execution will be passed back to callback in send-event\n(mr/receive-event reactor ($ \"hello-key\") (fn [_] \"response\"))\n\n;; Sends \"data\" to \"hello-key\" and waits for handler to call back\n(mr/send-event reactor \"hello-key\" \"data\" (fn [event]\n                                            ;; do job with response\n                                            ))\n```\n\n### Stream Processing\n\nTwo main concepts in stream processing are `channel` and `stream`. You\ncan publish information to `channel`, create arbitrary amount of streams\nout of any `channel` or `stream`.\n\n`stream` is a stateless event processor, that allows values that are\ngoing through it to be filtered or changed. It's very easy to build\nlarge processing graphs using this concept, since every mutation returns\nanother `stream` you can attach consumers to.\n\nIn order to compose streams, you can use `clojurewerkz.meltdown.streams/map*`, `filter*`, `reduce*`\nand `batch*` functions. They have signatures similar to the ones you're\nused to have in clojure. Applying `map*` with `inc` function on the\nchannel will create a new `stream` that contains all the values\nincremented by one.\n\n#### Stream Flushing\n\nStream processing in Reactor is (mostly) lazy. To begin feeding a channel\nthe values, you need to flush it with `clojurewerkz.meltdown.streams/flush`\nfirst.\n\n#### `map*` operation\n\nFor example, let's create a channel where we push integers, and two\nstreams with attached consumers that would calculate incremented and\ndecremented values for incoming ints:\n\n```clj\n(require '[clojurewerkz.meltdown.streams :as ms :refer [create consume accept map*]])\n\n(let [channel (create)\n      incremented-values (map* inc channel)\n      decremented-values (map* dec channel)]\n  (consume incremented-values (fn [i] (println \"Incremented value: \" i)))\n  (consume decremented-values (fn [i] (println \"Decremented value: \" i)))\n  (accept channel 1)\n  (accept channel 2)\n  (accept channel 3)\n  (ms/flush channel))\n;; =\u003e Incremented value:  2\n;; =\u003e Decremented value:  0\n;; =\u003e Incremented value:  3\n;; =\u003e Decremented value:  1\n;; =\u003e Incremented value:  4\n;; =\u003e Decremented value:  2\n```\n\nYou can also apply `map*` to streams that were already \"mapped\", reduced\nfiltered or batched:\n\n```clj\n(require '[clojurewerkz.meltdown.streams :as ms :refer [create consume accept map*]])\n\n(let [channel (create)\n      incremented-values (map* inc channel)\n      squared-values (map* (fn [i] (* i i)) incremented-values)]\n  (consume squared-values (fn [i] (println \"Incremented and squared value: \" i)))\n  (accept channel 1)\n  (accept channel 2)\n  (ms/flush channel))\n;; =\u003e Incremented and squared value:  4\n;; =\u003e Incremented and squared value:  9\n```\n\n#### `filter*` operation\n\n`filter*` would filter values that go through it and only pass ones for\nwhich predicate matches further:\n\n```clj\n(require '[clojurewerkz.meltdown.streams :as ms :refer [create consume accept filter*]])\n\n(let [channel (create)\n      even-values (filter* even? channel)\n      odd-values  (filter* odd? channel)]\n  (consume even-values (fn [i] (println \"Got an even value: \" i)))\n  (consume odd-values (fn [i] (println \"Got an odd value: \" i)))\n  (accept channel 1)\n  (accept channel 2)\n  (accept channel 3)\n  (accept channel 4)\n  (ms/flush channel))\n;; =\u003e Got an odd value:  1\n;; =\u003e Got an even value:  2\n;; =\u003e Got an odd value:  3\n;; =\u003e Got an even value:  4\n```\n\n#### `reduce*` operation\n\n`reduce*` works pretty much same way as `reduce` in clojure works,\nexcept for it gets values from the stream and holds last accumulator\nvalue:\n\n```clj\n(require '[clojurewerkz.meltdown.streams :as ms :refer [create consume accept reduce*]])\n\n(let [channel (create)\n      res (atom nil)\n      sum (reduce* #(+ %1 %2) 0 channel)]\n  (consume sum #(reset! res %))\n  (accept channel 1)\n  (accept channel 2)\n  (accept channel 3)\n  (ms/flush channel)\n  @res)\n;; =\u003e 6\n```\n\n#### `batch*` operation\n\nFor buffered operations, for example, when you'd like to have several\nvalues batched together, and only bundled values are of interest for\nyou, you can use `batch*` that only emits values when buffer capacity is\nreached and buffer is flushed.\n\n#### Custom streams\n\nIf these four given operations are not enough for you and you'd like to\ncreate a custom stream, it's quite easy as well. For that, there's a\n`custom-stream` operation available. For example, you'd like to create\na stream that will only dispatch every 5th value further. For state,\nyou can use let-over-lambda:\n\n```clj\n(defn every-fifth-stream\n  \"Defines a stream that will receive all events from upstream and dispatch\n   every fifth event further down\"\n  [upstream]\n  (let [counter (atom 0)]\n    (custom-stream\n     (fn [event downstream]\n       (swap! counter inc)\n       (when (= 5 @counter)\n         (reset! counter 0)\n         (accept downstream event)))\n     upstream)))\n```\n\nYou can use custom streams same way as you usually use internal ones:\n\n```clj\n(let [channel (create)\n      res (atom nil)\n      incrementer (map* inc channel)\n      inst-every-fifth-stream (every-fifth-stream incrementer)]\n  (consume inst-every-fifth-stream #(reset! res %))\n  (accept channel 1)\n  (println @res)\n  (accept channel 2)\n  (println @res)\n  (accept channel 3)\n  (println @res)\n  (accept channel 4)\n  (println @res)\n  (accept channel 5)\n  @res)\n;; =\u003e nil\n;; =\u003e nil\n;; =\u003e nil\n;; =\u003e nil\n;; =\u003e 6\n```\n\n### Building declarative graphs\n\nYou can also build processing graphs in a declarative manner. For example,\nlet's create a graph that will increment all incoming values, then aggregate\na sum of them and put that sum to atom.\n\nFor these examples, you should use `clojurewerkz.meltdown.stream-graph` namespace\ninstead of usual `streams` one.\n\n```clj\n(ns my-stream-graphs-ns\n  (:use clojurewerkz.meltdown.stream-graph))\n\n(let [res (atom nil)\n      channel (graph (create)\n                     (map* inc\n                         (reduce* #(+ %1 %2) 0\n                                    (consume #(reset! res %)))))]\n  (accept channel 1)\n  (accept channel 2)\n  (accept channel 3)\n  @res)\n;; =\u003e 9\n```\n\n#### Attaching and detaching graph parts\n\nIf you see that your graph is too deeply nested, and you'd like to split it\nin parts, you can use `attach` and `detach` functions. For example, here's\na graph that will increment each incoming value and calculate sums of\neven and odd values separately:\n\n```clj\n(let [even-sum (atom nil)\n      odd-sum (atom nil)\n      even-summarizer (detach\n                       (filter* even?\n                                (reduce* #(+ %1 %2) 0\n                                         (consume #(reset! even-sum %)))))\n\n      odd-summarizer (detach\n                      (filter* odd?\n                               (reduce* #(+ %1 %2) 0\n                                        (consume #(reset! odd-sum %)))))\n      summarizer #(+ %1 %2)\n      channel (graph (create)\n                     (map* inc\n                           (attach even-summarizer)\n                           (attach odd-summarizer)))]\n\n  (accept channel 1)\n  (accept channel 2)\n  (accept channel 3)\n  (accept channel 4)\n\n  @even-sum\n  ;; =\u003e 6\n\n  @odd-sum\n  ;; =\u003e 8\n  )\n```\n\n## Error handling\n\nWhenever an Exception occurs inside of your processing pipeline,\ndownstreams won't receive any events. In order to debug your\npipeline and see what exactly happened, you should subscribe\nto the `Exception` events by using `on-error`.\n\nMost generic case would be:\n\n```clj\n(mr/on-error r Exception (fn [event]\n                           (println event)))\n```\n\nSo whenever __any__ `Exception` (of any type) occurs inside your\nprocessing pipeline, your handler will be executed. You can also use\nsub-classes:\n\n```clj\n(mr/on-error r RuntimeException (fn [event]\n                                  (println event)))\n```\n\nIn this case your handler will be only triggered when type of an occurred\nexception is `RuntimeException` or one of the derived classes.\n\nYou can have more than one handler per `Exception` type.\n\n## Practical applications\n\nYou can use `reactor` for all kinds of events in your systems. Event can\nrepresent an error, alarm or some other piece of information another\npart of system should be aware of. Reactor calls do not block, therefore\nshould be used in cases when you'd like to fire-and-forget the event.\n\nUsing reactive approach has it's advantages, for example, if you're\nresponding to TCP socket connection, you can dispatch an event to\nreactor, continue listening and be certain that a worker will pick it up\nand take care of it.\n\nYou should be aware of the fact that if your handlers never finish (for\nexample, there's an endless loop inside one of handlers), you'll\neventually run out of available handlers, and won't be able to proceed.\n\n## Functional Programming Paradigms\n\nIn Meltdown, we support three basic primitives of functional composition,\nsuch as Monoid, Functor and Foldable. Monoids are used to build either \nsequences (lists, groups), searching for maximum or minimum, finding \na sum of the stream and so on.\n\n### Monoids \n\nMonoid consists of 2 operation: `mempty` (sometimes called `mzero`) and \n`mappend`. One of our monoid instances is a list:\n\n```clj\n(extend-protocol Monoid\n  clojure.lang.PersistentVector\n    (mempty [_] [])\n    (mappend [old new] (conj old new)))\n```\n\nThis monoid has a constructor `mempty` that returns an empty list, and\n`mappend` which appends new elements of the stream to the list.\n\nAnother example of Monoid is `MinValue`:\n\n```clj\n(deftype MinValue [a]\n  clojure.lang.IDeref\n  (deref [_] a))\n\n(extend-protocol Monoid\n  MinValue\n  (mempty [_] (MinValue. (Double/POSITIVE_INFINITY)))\n  (mappend [old new] (MinValue. (min @old new))))\n  \n;; (mconcat (MinValue. 1) [1 2 3 4])\n;; =\u003e #\u003cMinValue@45061e92: 1\u003e\n```\n\n`MinValue` is just a container for a value that it holds. `mempty` would\nin that case be either an \"empty value\" (MinEmpty, if you wish), but for simplicity\nI decided to construct it with Positive Infinity, since everything is less than positive\ninfinity. `mappend` operation finds a minimum of old and new value and puts it\nback into `MinValue` context.\n\n### Functor \n\nWe use functors to distinquish between the ways we \"go into\" the context. Functor\nhas only 1 function in it's interface, and it's `fmap`. For example, want to run `inc`\n(increment by 1) within a list, we would use `map`, that would apply our function to\nthe every element of the list. Same thing for our `group` (which is essentially a \nhashmap of `{group-name [group-value]}`), we would have to apply this function to \n_each element_ of `[group-value]` for each `group-name`. \n\nIf we wanted to fun `inc` on `MinValue`, we would just extract the value from `MinValue`\ncontext, run `inc` on it and pack it back into `MinValue`:\n\n```clj\n(extend-protocol Functor\n  MinValue\n  (fmap [v f]\n    (MinValue. (f @v))))\n;; (fmap (MinValue. 5) inc)\n;; =\u003e #\u003cMinValue@1bf9ff6e: 6\u003e\n```    \n\nSo, Functor would be a convenient way apply functions in different contexts. \n\n### Foldable\n\nWhenever we have some context, we want to some way to fold it. Folding mostly \nmakes sense for collection. \n\nInside meltdown, we support 2 types of folds by default: \n\n  * fold of list (which is essentially `reduce`)\n\n```\nfold :: [a] -\u003e ([a] -\u003e b) -\u003e b\n```\n  \n  * and fold on groups, which would reduce values of an entire group to a \n  single value:\n  \n``` \n;; Pardon my pseudotypesignature \nfold :: {a [b]} -\u003e ([b] -\u003e c) -\u003e {a c}\n```\n\nYou may wonder, what it gives us in terms of stream processing and why that even\nmatters. Let's check out a simple example:\n\n```clj\n(graph (create :env env)\n       ;; (1)\n       (map* (fn [i] [(if (even? i)\n                       :even\n                       :odd)\n                     i])\n             ;; (2)\n             (mappend* {}\n                       ;; (3)\n                       #(= 5 (last (:odd %)))\n                       ;; (4)\n                       (fmap* inc\n                              ;; (5)\n                              (fold* +\n                                     (consume #(reset! res %)))))))\n```                          \n\nHere we have several steps:\n  * (1) at first we're transforming our incoming numbers into a vector of `[\u003cparity\u003e, number]`. For example, for 1 it would return `[:odd 1]` and `[:even 2]` for 2.\n  * (2) these tuples are then passes into `mappend*`, which is initialized with `{}` (group/hash) monoid. Since there on all our items will be collected into the hashmap, holding hash of `{\u003cparity\u003e [\u003citems\u003e]}`:\n\n```clj\n{:even [2 4], :odd [1 3 5]}\n```  \n  \n  * (3) here, we just specify a \"flush\" condition. So whenever we see \"5\", we flush. Although that may be as well any other logical predicate.\n  * (4) now, we want to apply `inc` to every element of each group. So, we apply inc to each element in each key-value pair:\n\n```clj\n{:even (map inc [2 4]), :odd (map inc [1 3 5])}\n;; =\u003e {:even [3 5], :odd [2 4 6]}\n```  \n\n  * (5) we can now use `fold` to get sums of all elements for each group, obtaining our final result: \n  \n```clj\n{:odd 12, :even 8}\n```\n\nThis was a simplifed example, but even here you may notice that having an ability to \niterate over different colleciton types in different ways may help to simplify stream\nprocessing code and avoid boilerplate. By retaining semantics of operations (operation\nthat executed on each item within the context, or item that folds all the values \nwithin the relevant context), you can build your own abstractions and make your code\neasier to write, understand, debug and extend.\n\n## Performance\n\nBasic throughput tests are included. They're set up in the exact same way\nReactor's throughput tests are done, therefore you can compare outputs\ndirectly. Overhead is insignificant.\n\n## Supported Clojure Versions\n\nMeltdown requires Clojure 1.6+.\n\n\n## Continuous Integration Status\n\n[![Continuous Integration status](https://secure.travis-ci.org/clojurewerkz/meltdown.png)](http://travis-ci.org/clojurewerkz/meltdown)\n\n\n\n## Meltdown Is a ClojureWerkz Project\n\nMeltdown is part of the [group of Clojure libraries known as ClojureWerkz](http://clojurewerkz.org), together with\n * [Langohr](https://github.com/michaelklishin/langohr)\n * [Elastisch](http://clojureelasticsearch.info)\n * [Cassaforte](http://clojurecassandra.info)\n * [Monger](http://clojuremongodb.info)\n * [Titanium](http://titanium.clojurewerkz.org)\n * [Welle](http://clojureriak.info)\n * [Neocons](http://clojureneo4j.info)\n * [Quartzite](https://github.com/michaelklishin/quartzite) and several others.\n\n\n## Development\n\nMeltdown uses [Leiningen](http://leiningen.org). Make\nsure you have it installed and then run tests against supported\nClojure versions using\n\n    lein all test\n\nThen create a branch and make your changes on it. Once you are done\nwith your changes and all tests pass, submit a pull request on GitHub.\n\n\n\n## License\n\nCopyright (C) 2013-2016 Michael S. Klishin, Alex Petrov, and the ClojureWerkz team.\n\nDouble licensed under the [Eclipse Public License](http://www.eclipse.org/legal/epl-v10.html) (the same as Clojure) or\nthe [Apache Public License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojurewerkz%2Fmeltdown","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclojurewerkz%2Fmeltdown","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojurewerkz%2Fmeltdown/lists"}