fun-map icon indicating copy to clipboard operation
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.