A Clojure/script library that provides the kind of arrows you need on an impulse as well as some arrow transformers called fletchings.
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."
[net.clojars.unexpectedness/threading "0.4.1"]
(ns my-ns
(:require [threading.core :refer :all]))
I came up with this library to circumvent limitations of some arrows from clojure.core
and other threading libraries, namely the unability for the programmer to decide whether an arrow should thread its expression in a specific threading slot or not.
Let's observe how clojure.core/cond->
works:
(cond-> THREADED-EXPR
true THREADING-SLOT
false THREADING-SLOT)
The problem I want to underline is the fact conditions in this threading arrow cannot receive the threaded expression. Similarly, the cases matching each condition must receive the threaded expression and we don't have control over this either.
Same problem with pallet.thread-expr/if->
:
(-> 1
(if-> true
THEN-THREADING-SLOT
ELSE-THREADING-SLOT))
the condition cannot receive the threaded expr and the cases must receive it no matter what.
All subforms in a threading form are considered threading slots by arrows in this library.
As an equivalent of constantly
, the <-
antithreading arrow can be used to make a form evaluate the threaded expression, discard the result and return an arbitrary value instead.
Thus, if->
becomes:
(-> 1 (if-> (<- true)
inc
(<- 0)))
or just as possibly,
(-> 1 (if-> (-> number? not)
(<- (throw (IllegalArgumentException. "not a number")))
inc))
This is the central piece and the first goal of this library: to provide obvious and dearly missed arrows such as cond->
and if->
without sacrificing on flexibility thanks to the antithreading arrow <-
.
The secondary goal of this lib is to provide arrow transformers called fletchings. They look pretty nice on the screen :-).
All of the arrows below come with both a ->
variant and a ->>
variant.
There is a great and simple way to define them and you're invited to collaborate!
As said above, <-
is an equivalent to constantly
. Use <-
in the context of a form threading in the style of ->
, and <<-
in the context of arrows like ->>
(-> 1 (<- 42)) ;; => 42
(->> 1 (<<- 42)) ;; => 42
if->
, when->
, if-not->
, when-not->
and->
, or->
, not->
Contrary to their Clojure counterparts (if
, when
, etc ...), these arrows (except not->
) return the threaded value rather than nil
when they "fail".
Consider:
(defn xxx [x]
(-> x
(when-> string? (str "_abc"))
vector))
(xxx "bird")
;; => ["bird_abc"]
(xxx 123)
;; => [123] (rather than [nil])
let->
, binding->
, when-let->
.
(-> 1
(let-> [double (* 2)]
(+ double)))
;; => 3
juxt->
& juxtm->
(-> 1 (juxt-> (/ 2) dec -))
;; => '(1/2 0 -1)
(-> 1 (juxtm-> :half (/ 2) :decd dec :neg -))
;; => '{:half 1/2, :decd 0, :neg -1}
doto->
(-> 123
(doto-> (str " -> the initial value")
println)
inc
(doto->> (str "New value is : ")
println))
;; 123 -> the initial value
;; New value is: 124
;; => 124
dotos
The dotos
macro will work in a similar way:
(dotos (inc 1) (println "Status: done"))
;; Status: done
;; => 2
Although it is not a threading arrow strictly speaking it will thread the passed value into any threading form that happens to be at the first level in its body.
Consider:
(dotos (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->
& filter->
(-> [1 2 3] (map-> (+ 3))
;; => (4 5 6)
(-> [1 2 3] (filter-> odd?))
;; => (1 3)
Similarly, mapv->
, 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 arrow(-> x (>- (-> y))) <=> (-> y x) <=> (-> x (>-> y))
(-> x (>- (->> y))) <=> (->> y x) <=> (-> x (>->> y))
(->> x (>>- (-> y))) <=> (-> x y) <=> (->> x (>>-> y))
(->> x (>>- (->> y))) <=> (->> x y) <=> (->> 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))
>-args
So that an arrows applies to a form's subforms rather than to the form itself.
(-> 1 (>-args (-> (+ inc (/ 2))))) ;; 5/2
;; is equivalent to
(let [x 1]
(+ (-> x inc) (-> x (/ 2))))
Similarly:
(-> 1 (>-args (->> (+ inc (/ 2))))) ;; 4
(->> 1 (>>-args (-> (+ inc (/ 2))))) ;; 5/2
(->> 1 (>>-args (->> (+ inc (/ 2))))) ;; 4
The o-
fletching stores the threaded expression on the stack then threads it to the threading form. Any deep occurence of the -o
arrow in the threading form will thread this stored value to the next expressions.
(-> 1 (o- (+ 100 (-o (/ 2))))) ;; => 101.5 (+ 100 1 (/ 1 2))
(-> 1 (o- (+ 100 (-oo (/ 2))))) ;; => 103 (+ 100 1 (/ 2 1))
(->> 1 (oo- (+ 100 (-o (/ 2))))) ;; => 101.5 (+ 100 (/ 1 2) 1)
(->> 1 (oo- (+ 100 (-oo (/ 2))))) ;; => 103 (+ 100 (/ 2 1) 1)
The challenge lying in defining both the ->
and ->>
variants, observe the actual definition of doto->/doto->>
:
(defthreading doto ;; name prefix (optional). You can also write "doto :suffix".
"Threads the expr through the forms then returns the value of
the initial expr.";; Doc body (optional)
[-> "Threads like `->`." ;; Doc suffixes (optional)
->> "Threads like `->>`."]
[expr & forms]
`(let [result# ~expr]
;; &threading-variant will be succesively bound to '-> and '->>
(~&threading-variant result# ~@forms)
result#))
See defthreading
for details and the sources for more simple examples to work from.
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 welcomed if they are driven by an impulse.pp->
is a bit weird at times.Copyright © 2024 unexpectedness
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