fun-map
fun-map copied to clipboard
a map blurs the line between identity, state and function
= fun-map, a data structure blurs the line between identity, state and function :icons: font
image:https://github.com/robertluo/fun-map/actions/workflows/main.yml/badge.svg[CI,link=https://github.com/robertluo/fun-map/actions/workflows/main.yml] image:https://img.shields.io/clojars/v/io.github.robertluo/fun-map.svg[Clojars Project,link=https://clojars.org/io.github.robertluo/fun-map] image:https://cljdoc.xyz/badge/io.github.robertluo/fun-map[cljdoc,link=https://cljdoc.xyz/d/io.github.robertluo/fun-map/CURRENT]
Fun-map is a Clojure(Script) map data structure. The difference between it and a normal map is: the values are evaluated until you take the value of it.
== Usage scenarios
=== as a lazy map
If you put a clojure.lang.IDeRef
instance like a deferred value (created by defer
) or future into the map, deref
will be automatically called when you access it. This way, the map can be thought of as a lazy map.
[source,clojure]
(def m (fun-map {:a (future (+ 5 3))})) (:a m) ;=> 8
TIP: You can specify :keep-ref
to disable this default feature, and use fnk
to force it to be lazy for individual values.
=== dependent injection
Your values can now depend on the map itself, most commonly on other values.
[source,clojure]
(def m (fun-map {:numbers (range 10) :cnt (fnk [numbers] (count numbers)) :sum (fnk [numbers] (apply + numbers)) :average (fnk [cnt sum] (/ sum cnt))}))
This feature is similar to https://github.com/plumatic/plumbing[Plumatic Plumbing's graph], it even has the same name fnk
macro! The difference is that fun-map is a normal map, you could update the values (and the fnk
function) at any time. You could use a plain map if you want to test some complex computations.
=== lifecycle management
A fun-map can track the realization order of its values by specifying a :trace-fn
option, combine this and the dependency injection nature, leads to a simple lifecycle management feature.
life-cycle-map
is a fun-map that can orderly close your lifecycle components.
[source,clojure]
(def system (life-cycle-map {:component/a (fnk [] (closeable 100 #(println "halt :a"))) ;;<1> :component/b (fnk [:component/a] (closeable (inc a) #(println "halt :b")))})) (touch system) ;;<2> (halt! system) ;;<3>
<1> closeable
function turns a value to a closeable value, the specified close function will be called when the whole system is being close
.
<2> touch
travels the system, and makes potential lazy values realized, in a real application, you may not want to do it.
<3> halt!
is a function that closes a closeable value.
== Example
https://github.com/robertluo/waterfall[Waterfall library] uses life-cycle-map
to do dependency injection and lifecycle management, turns Kafka usage into map accessing.
== API
The https://cljdoc.xyz/d/robertluo/fun-map/CURRENT[complete API documents] are on cljdocs.
Some of the most frequently used APIs are:
-
fun-map
-
fnk
(macro) -
fw
(macro)
[source,clojure]
(defn book-of [connection author] (-> (fun-map {:datomic/db (fnk [:datomic/connection] (d/db connection)) ;;<1> :books (fnk [:datomic/db author] (d/q '[:find ?e :in $ ?name :where [?e :book/author ?name]] db author)) :book-count (fnk [books] (count books))}) (assoc :datomic/connection connection :author author))) ;;<2> (book-system (d/connect "datomic:mem://books") "Harry Potter")
<1> fnk
accepts namespaced keywords.
<2> by assoc
key-value pairs to a fun-map, you turn Datomic API into a map.
== Further read
- xref:doc/rational.adoc[Rational]
- xref:doc/change_log.adoc[Changes]
- xref:doc/concepts.adoc[Concepts]
== Development
Fun-map using https://github.com/lambdaisland/kaocha[kaocha] as the test runner.
- use
clj -M:test --watch
to automatically run tests whenever code changes.
== License
Copyright © 2018, 2019, 2022 Robertluo
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.