This is interesting to read having worked with Clojure, but never Haskell or CL. I expected the Haskell examples to look alien and the CL to look familiar, but the idiomatic Clojure solutions to the examples are almost identical to the Haskell solutions. E.g.
take 5 . filter (not . p) . drop 3
becomes
(->> s (drop 3) (filter (complement p)) (take 5)) ; for some sequence s
I think it is also true of Clojure that it strives to have many small functions with a high degree of composability.
I use threading (->>/->) macros in Elisp too. Might not be "idiomatic" but to me malleability of LISPs has always been the biggest selling point. Although I don't see threading macros a lot, functional libraries are pretty popular in newer Elisp packages.
Now there's a different discussion to be had about laziness and immutability but for a lot of stuff it doesn't really come into play.
We have laziness in Clojure but don't think we have stream fusion. Yet the functional approach works just fine for most things and is preferred.
Even in JS which doesn't even have built-in laziness most new stuff uses a functional approach (underscore,lodash etc) wherever possible too.
CL is just unique in that it has a very long history and a huge standard library. They have many of these monolithic functions that other languages simply don't have built-in.
Assuming that (-->) is either a typo of Clojure's ->> or ->, or some CL macro that operates the same (I don't know any CL, admittedly), in Haskell there is the somewhat common & (as in, available in the standard library and the widely-used lens library, but not part of the prelude and a lot of folks seem to eschew it in favor of composing in "the other direction" with . and $), which is defined as `flip ($)` (i.e. reverse function application) and visually ends up working the same as ->>:
myList
& map someFunction
& filter somePredicate
& sum
Other ML-ish languages typically provide this operator but under a different name, |> being a common one. One important note is that the mechanics here are a bit different than threading macros in lisp, since you're explicitly building the AST you want and using operator precedence (and implicit/automatic currying) to get the visual effect, rather than calling a rewrite rule. This means you can't have a straightforward reproduction of Clojure's -> macro, although in practice this never really matters, and when it does you can still fake it with flip (or, more readably, parens and a lambda).
This comes for free from Haskell's automatic partial application (that arises from Haskell's pervasive use of function currying). So several different Haskell variants of Clojure's `compose` correspond to different versions of Clojure's threading macros.
Clojure's threading macros arise because it's a little bit more awkward in Clojure to partially apply functions (either anonymous functions with # or use `partial`) and it reads more fluently to just use threading macros there.
What's the syntax in Haskell for thread-first (-> in Clojure) which inserts the result of the previous operation as the first argument, versus thread-last (->>) which inserts it at the end, so is more amenable to composed curried functions?
Ah I was wrong! That's what I get for not coding in Haskell in a year and not checking my answer with GHC.
Function application always has higher precedence than infix operators so I can't emulate thread-first with functions in Haskell. If that wasn't the case, this would work (this was what I was thinking of)
(|*>) :: b -> (a -> b -> c) -> (a -> c)
(|*>) x f = (\y -> f y x)
xs = 5
|*> take [1, 2, 3]
Otherwise, I can come close with this.
import Data.Function ((&))
infix 9 *-
(*-) :: (a -> b -> c) -> b -> (a -> c)
(*-) f x = (\y -> f y x)
xs :: [Int]
xs = 5
& take *- [1, 2, 3]
& filter (/= 1)
& head
& take *- [1, 2, 3]
Using right-associativity of function types, and by sugaring the lambda into another function argument, we can rewrite this as
(*-) :: (a -> b -> c) -> b -> a -> c
(*-) f x y = f y x
This reveals that (*-) is simply flip, which is in the Prelude (re-exported from Data.Function). Alternatively, Hoogling the original type signature will reveal the same thing.
So the example doesn't require any new functions:
import Data.Function ((&))
xs :: [Int]
xs =
5
& flip take [1, 2, 3]
& filter (/= 1)
& head
& flip take [1, 2, 3]
In general, it's common in Haskell to use flip, curry/uncurry, and other higher-level functions to manipulate functions so they fit into the needed context.
I think there are some pipe-like operators in the popular Lens library (and presumably others as well). However, writing Haskell, you quickly get used to everything being backwards.
I very much enjoyed a Kmett talk where he described needing to parameterize some lens thing with an index, a monad it would operate under, the source focus, the target focus, and three different containing structures, with some pieces repeated... and then realizing the type arguments said `i m a s t a b u` and decided he had probably gone too far.
While I have found the majority of my interactions with Haskell (and ML family languages) deeply satisfying, I have never really used Lens in anger, having been put off each time I've tried to get into it. Although, I remain slightly simpathetic since - as you say - there is some theory underlying it all.
I may be mistaken, but I think I have seen a handful of "Lens-lite" type libraries for use in production since it's such a large library, which seems.. uh.. suboptimal.
Not really. In the example, function composition, `.`, joins the functions together.
You can smash a list of functions together but they'd all have to take and return the same type, and the list would introduce commas and square brackets, so it wouldn't look quite the same.