9 ms·
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
by jetrink 6y ago
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.
- behnamoh 6y agoIs there something similar to (->>) in Haskell? Edit: corrected “—>”.
- Ixiaus 6y ago`.` is function composition in Haskell (used in the example).
- eru 6y agoInterestingly function composition is also the mapping operator (fmap) for the corresponding functor.
- rbonvall 6y agoYou can create it yourself: Prelude> s ->> f = f s Prelude> [1..100] ->> take 10 ->> filter (not . odd) ->> drop 3 [8,10]
- jim-jim-jim 6y agoI think `&` is this function. In Data.Function.
- eru 6y agoOr you can use >>> from Data.Function, if you prefer to go point-free.
- garethjrowlands 6y agoNot 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.
- eru 6y agoYou might be able to do some type trickery with heterogeneous lists? But I don't think it would be worth it.
- truculent 6y agoI 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.
- thom 6y agoI find the Lens operators in Haskell just the most hilarious API in all of computing, even knowing that there is some sense to it.
- dllthomas 6y agoI 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.
- eru 6y agoWhat's interesting is that Ed Kmett isn't even a dyed in the wool Haskeller. He has more than a foot in the C++ world. 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.
- truculent 6y agoWhile 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.
- embwbam 6y agoIs this what you're looking for? (&) http://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Function.html#v:-38- http://hackage.haskell.org/package/base-4.14.0.0/docs/Data-F...
- nimih 6y agoAssuming 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).
- eru 6y agoIf you really want to, Haskell also has a few macro systems. They are just not used nearly as much as in Lisp.
- dwohnitmok 6y agoThis 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. However, Clojure can do fancier things with variants of the common threading macros (https://github.com/rplevy/swiss-arrows https://github.com/rplevy/swiss-arrows)
- thom 6y agoWhat'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?
- dwohnitmok 6y agoAh 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]
- thewakalix 6y agoUsing 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.
- thom 6y ago(filter (complement p)) is just (remove p), right?
- auganov 6y agoI 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.