jank programming language - Clojure/LLVM/C++
Generate a movie index
jank has very powerful capabilities for representing and transforming arbitrary data. Here, idiomatic usages of reduce, zipmap, repeat, and merge-with help create an index from genre to movie id with ease. No lenses are required for working with nested data.
(def movies {:the-weather-man {:title "The Weather Man"
:genres [:drama :comedy]
:tomatometer 59}
:nightcrawler {:title "Nightcrawler"
:genres [:drama :crime :thriller]
:tomatometer 95}
:the-bourne-identity {:title "The Bourne Identity"
:genres [:action :thriller]
:tomatometer 84}})
(def genre->movie (reduce (fn [acc [id movie]]
(let [{:keys [genres]} movie
genre->this-movie (zipmap genres (repeat [id]))]
(merge-with into acc genre->this-movie)))
{}
movies))
; genre->movie is now a useful index.
; =>
{:drama [:the-weather-man :nightcrawler],
:comedy [:the-weather-man],
:crime [:nightcrawler],
:thriller [:nightcrawler :the-bourne-identity],
:action [:the-bourne-identity]}
; We can look up all movies by genre.
(->> (genre->movie :thriller)
(map movies)
(sort-by :tomatometer))
; =>
({:title "The Bourne Identity",
:genres [:action :thriller],
:tomatometer 84}
{:title "Nightcrawler",
:genres [:drama :crime :thriller],
:tomatometer 95})
Convert bytes to human readable format
Beyond the traditional map, filter, and reduce, jank provides a powerful loop macro for more imperative-style loops while still being purely functional. Each loop has one or more corresponding recur usages which must be in tail position.
(defn size->human-readable
"Converts a size, in bytes, to a human readable format, such as 0 B, 1.5 kB,
10 GB, etc."
[size-in-bytes]
(if (< -1000 size-in-bytes 1000)
(str size-in-bytes " B")
(let [res (loop [acc size-in-bytes
suffixes "kMGTPE"]
(if (< -999950 acc 999950)
{:size acc
:suffix (first suffixes)}
(recur (/ acc 1000) (drop 1 suffixes))))]
(format "%.1f %cB" (float (/ (:size res) 1000)) (:suffix res)))))
(assert (= "0 B" (size->human-readable 0)))
(assert (= "57.0 kB" (size->human-readable (* 57 1000))))
Truncate a string to a max length
jank's strings, as well as most of its other data structures, are immutable. However, jank provides such powerful tools for working with data that mutability is very rarely a concern.
(def max-text-length 256)
(defn truncate
"Truncates the text to be no longer than the max length."
[text max-length]
(cond
(<= max-length 0)
""
(<= (count text) max-length)
text
:else
(str (subs text 0 (dec max-length)) "…")))
(assert (= "" (truncate "wowzer" 0)))
(assert (= "wow…" (truncate "wowzer" 4)))
Redefine any var
Every def or defn exists within a var, which is a stable, namespace-level container for values. Vars can be redefined to contain different values. with-redefs redefines a var within its body's scope, which is very useful for removing side effects from test cases or forcing functions to return specific values.
(defn post! [_request]
; Assuming this performs some network effect.
{:status 200
:body (pr-str {:order 7821})})
(defn submit-order! []
(let [request {:url "/submit-order"}
order (post! request)
order-body (-> order :body read-string)]
(if (contains? order-body :error)
; This is the code path we want to test.
{:error "failed to submit"
:request request
:response order-body}
(:order order-body))))
; Later on, in tests, skip the side effect by redefining.
(deftest submit-order
(testing "failed post"
(with-redefs [post! (fn [_]
; Fake error to see how the rest of the code handles it.
{:status 500
:body (pr-str {:error "uh oh"})})]
(is (= "failed to submit" (:error (submit-order!)))))))