A Clojure library that provides a small set of threading arrows, the kind of arrows you need on an impulse.
Penelope now appears before the suitors in her glittering veil. In her hand is a stout bow left behind by Odysseus when he sailed for Troy. "Whoever strings this bow," she says, "and sends an arrow straight through the sockets of twelve ax heads lined in a row -- that man will I marry."
[threading "0.1.8"]
(ns my-ns
(:require [threading.core :refer :all]))
(-> the-value
;; obvious arrows
(when-> coll? (-> (reduce +)))
;; anti-threading arrows
(if-> (and-> number? (<- *number-accepted?*))
inc
(tap (swap! str-count inc))
;; arrow fletchings
(if-> keyword?
(when-not->> (>- (-> str (str/ends-with? "xyz")))
(println "Not a valid string")
(<<- (throw (IllegalArgumentException. "Not a string")))))
(when-> (and-> number? (not-> (<- *number-accepted?*)))
(<- (throw (IllegalArgumentException. "Numbers not accepted"))))
(tap->> (println "Result:")))
All of the arrows below come with both a ->
variant and a ->>
variant.
All of their arguments are forms in which the threaded form will be injected, either at the beginning or the end, depending on which variant is used.
As such each argument of these threading arrows can be considered as a threading slot.
As a threading equivalent to constantly
, and as a way to return a value from a threading slot that does not depend on the threaded value, use the <-
anti-threading arrow:
Consider:
(-> 123
(when-not-> (or-> string? (<- *number-accepted?*))
(throw (IllegalArgumentException. "Not a string"))))
If you happen to be in the context of a ->>
-like threading arrow, use <<-
to antithread a value.
(when-> 123 (or->> (<<- *number-accepted?*) (re-matches #"[a-z]+"))
...)
if->
, when->
, if-not->
, when-not->
Contrary to their Clojure counterparts (if
, when
, etc ...), these threading arrows return the threaded value rather than nil
when the predicate fails.
Consider:
(defn xxx [x]
(-> x
(when-> string? (str "_abc"))
vector))
(xxx "bird")
;; => ["bird_abc"]
(xxx 123)
;; => [123] (rather than [nil])
and->
, or->
, not->
Like their Clojure counterparts, these threading arrows will return early and can be used to control the execution flow just like Clojure's and
& or
.
Consider:
(and (string? "abc") (vector "abc"))
;; => ["abc"]
(and-> "abc" string? vector)
;; => ["abc"]
tap->
tap->
gets is inspiration from Ruby's tap
tap()
publicYields x to the block, and then returns x. The primary purpose of this method is to “tap into” a method chain, in order to perform operations on intermediate results within the chain.
In Clojure, this gives:
(-> 123
(tap-> (println "-> the initial value"))
inc
(tap->> (println "New value is :")))
;; 123 -> the initial value
;; New value is: 124
;; => 124
tap
The tap
macro will work in a similar way:
(tap (inc 1) (println "Status: done"))
;; Status: done
;; => 2
Although it is not a threading arrow strictly speaking it will thread the tapped value into any threading form that happens to be at the first level in its body.
Consider:
(tap (inc 1)
(println "Status: done")
(->> (println "Result:")))
;; Status: done
;; Result: 2
;; => 2
pp->
and pp->>
will execute each form, threading them together, and will display at each step a debug line, then return the value from the last expr.
(pp->> {:a 1 :b 2}
(merge {:c 3})
(map (fn [[k n]] [k (inc n)]))
(into {}))
;; ->> : {:a 1, :b 2}
;; (merge {:c 3}) : {:c 3, :a 1, :b 2}
;; (map (fn [[k n]] [k (inc n)])): ([:c 4] [:a 2] [:b 3])
;; (into {}) : {:c 4, :a 2, :b 3}
;; => {:c 4, :a 2, :b 3}
map->
(map-> [1 2 3] (+ 3))
;; => [4 5 6]
Similarly, mapcat->
, map-keys->
& map-vals->
.
Used to shift from a thread-first to a thread-last threading-style, or conversely from thread-last to thread-first:
>-
to shift into thread-first mode in the context of a ->>
-like arrow>>-
to shift into thread-last mode in the context of a ->
-like arrowAn arrow fletching redefines where the threaded form will be injected in the threading form, while an arrowhead defines where this threaded form will be injected in the threading form inner threading slots.
(->> x (>- (-> y))) <=> (-> x y) <=> (->> x (>-> y))
(->> x (>- (->> y))) <=> (->> x y) <=> (->> x (>->> y))
(-> x (>>- (-> y))) <=> (-> y x) <=> (-> x (>>-> y))
(-> x (>>- (->> y))) <=> (->> y x) <=> (-> x (>>->> y))
Examples:
(->> 100 (>- (-> (/ 10)))) ;; expands to (-> 100 (/ 10))
(->> 10 (>- (->> (/ 100)))) ;; expands to (->> 10 (/ 100))
(-> (/ 10) (>>- (-> 100))) ;; expands to (-> 100 (/ 10))
(-> (/ 10) (>>- (->> 100))) ;; expands to (->> 100 (/ 10))
The challenge lying in defining both the ->
and ->>
variants, observe the actual definition of tap
:
(defthreading tap
"Threads the expr through the forms then returns the value of
the initial expr."
[-> "Threads like `->`." ;; Doc suffixes
->> "Threads like `->>`."]
[expr & forms]
`(let [result# ~expr]
;; &threading-variant will be succesively bound to '-> and '->>
(~&threading-variant result# ~@forms)
result#))
See defthreading
for details.
threading
?Compare:
(let [selection (fetch resource opts)]
(if (<= (count selection) 1)
(first selection)
selection)))
against:
(-> (fetch resource opts)
(if-> (-> count (<= 1)) first))
cond->
, maybe merge->
, etc... Contributions are welcome if they are driven by an impluse.pp->
is a bit weird at times.Copyright © 2018 TristeFigure
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close