Liking cljdoc? Tell your friends :D

threading

A Clojure library that provides a small set of threading arrows of the kind 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."

Usage

[threading "0.1.10"]
(ns my-ns
  (:require [threading.core :refer :all]))

API doc

Showcase

(-> 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:")))

Arrows

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.

The anti-threading arrow

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]+"))
  ...)

Conditionnals

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])

Boolean operators

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"]

Control flow

tap->

tap-> gets is inspiration from Ruby's tap

tap() public

Yields 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

Debugging

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}

Sequential operations

map->

(map-> [1 2 3] (+ 3))
;; => [4 5 6]

Similarly, mapcat->, map-keys-> & map-vals->.

Fletchings

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

What's the role of an arrow fletching compared to an arrowhead ?

An 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.

Equivalences

(->> 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))

Defining new arrows

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.

Why use threading ?

Readability

Compare:

(let [selection (fetch resource opts)]
  (if (<= (count selection) 1)
    (first selection)
    selection)))

against:

(-> (fetch resource opts)
    (if-> (-> count (<= 1)) first))

TODO

  • 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