Thread in Arbitrary Position in Clojure


Here's how to thread both first and last and middle in a single Clojure thread expression.

If the output of one form is passed to the input of the next in a long chain, it's often nice to express this as a thread.

Thread first

If, for every form in the thread, you always want to pass the output as the first argument of the next form, you can use "thread first", ->:

(-> (find-deep p? data "") (last) (clojure.string/split #" "))
["Carbon" "County" "is" "home" "to" "several" "top-notch" "biking" "trails," "offering" "something" "for" "cyclists" "of" "all" "levels." "The" "San" "Rafael" "Swell" "features" "the" "popular" "Wild" "Horse" "Canyon" "Trail," "a" "7.5-mile" "loop" "with" "rugged" "terrain" "and" "stunning" "desert" "views." "For" "mountain" "biking" "enthusiasts," "the" "Price" "Canyon" "Trails" "offer" "a" "series" "of" "loops" "ranging" "from" "4" "to" "10" "miles," "with" "moderate" "to" "difficult" "terrain." "The" "Helper" "to" "Sunnyside" "trail" "provides" "a" "scenic" "15-mile" "ride" "along" "an" "old" "rail" "line," "perfect" "for" "those" "looking" "for" "a" "longer" "ride." "These" "trails" "offer" "diverse" "landscapes," "from" "desert" "canyons" "to" "mountainous" "terrain," "making" "Carbon" "County" "a" "prime" "destination" "for" "cycling."]

Never mind the implementation of find-deep. But for the curious, it's walking a hiccup tree to find the text of a paragraph that you see in the output.

Thread last

If, for every form in the thread, you always want to pass the output as the last argument of the next form, you can use "thread last", ->>:

(def words ["Carbon" "County" "is" "home" "to" "several" "top-notch" "biking" "trails," "offering" "something" "for" "cyclists" "of" "all" "levels." "The" "San" "Rafael" "Swell" "features" "the" "popular" "Wild" "Horse" "Canyon" "Trail," "a" "7.5-mile" "loop" "with" "rugged" "terrain" "and" "stunning" "desert" "views." "For" "mountain" "biking" "enthusiasts," "the" "Price" "Canyon" "Trails" "offer" "a" "series" "of" "loops" "ranging" "from" "4" "to" "10" "miles," "with" "moderate" "to" "difficult" "terrain." "The" "Helper" "to" "Sunnyside" "trail" "provides" "a" "scenic" "15-mile" "ride" "along" "an" "old" "rail" "line," "perfect" "for" "those" "looking" "for" "a" "longer" "ride." "These" "trails" "offer" "diverse" "landscapes," "from" "desert" "canyons" "to" "mountainous" "terrain," "making" "Carbon" "County" "a" "prime" "destination" "for" "cycling."])
(->> words (take 10) (clojure.string/join " "))
"Carbon County is home to several top-notch biking trails, offering"

Note that in the case of last, which only has one parameter, it doesn't matter if you thread first or last, the value is always sent as the only argument.

Thread mixed

But what if, for this thread of forms, you don't have a consistent pattern in the position of the parameter you're trying to pass to? Surprisingly, this exists even in the standard library with clojure.string/split, which takes data to split as the first argument and clojure.string/join which takes data to join as the last argument. These two often work together. How do we include them each in a thread expression?

There is another threading operator, "thread as", as->. It allows us to name the result of the first form and then use that name in the subsequent forms, positioning arbitrarily. This allows us to write a thread expression. But it also requires us to place the input in the successive forms explicitly.

(as-> (find-deep p? data "") v (last v) (clojure.string/split v #" ") (take 10 v) (clojure.string/join " " v))
"Carbon County is home to several top-notch biking trails, offering"

You have now been initiated into the Guild of the Weavers, Bobbin. Go, seek the swans.