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. 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.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.
Edit: corrected “—>”.
Prelude> s ->> f = f s
Prelude> [1..100] ->> take 10 ->> filter (not . odd) ->> drop 3
[8,10]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.
He's quite brilliant, but I still haven't really gotten used to the full power of lenses, yet. I only used them in simple ways.
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.
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).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.
However, Clojure can do fancier things with variants of the common threading macros (https://github.com/rplevy/swiss-arrows)
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] (*-) :: (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. xs :: [Int]
xs =
5
& (`take` [1, 2, 3])
& filter (/= 1)
& head
& (`take` [1, 2, 3])
But I'm not sure that's a better style than flip.I really like `on` from Data.Function, too.
xs =
5
& (\n -> take n [1, 2, 3])
& filter (/= 1)
& head
& (\n -> take n [1, 2, 3])It depends on what you are used to and your tolerance thresholds.
In a code base I worked on professionally, I came across the following idiom
f a b `flip` d
which at first used to confuse me to no end. Later I figured out that you are supposed to read it as: f a b <place-holder> d
and thus \c -> f a b c d
In the same vein that Scala uses an underscore in somewhat implicit anonymous function syntax.I still wasn't really happy with that usage, but at least I see why someone thought it would make sense.
(Mostly I wasn't so happy because it only works for the penultimate argument. If the syntax had worked out so that
f a `flip` c d
would mean \b -> f a b c d
I would have been more amenable to the idiom. But you need the ugly f a `flip` c `flip` d
for that case. And that's clearly worse than the lambda syntax. flip :: (a -> b -> c) -> b -> a -> c
[1]: https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelu...