6 ms·
20 cool Clojure functions
- guard-of-terra 12y agoMay I present to you a list of cool functions Clojure doesn't have? map-keys, map-values, map-entries, group-by-1, group-by-2 on [[k, v]], zip-with [k] -> [[k v]]. That's what you immediately need if you do any data processing. Had to write those myself.
- Skinney 12y agoDon't understand why you would need seperate functions for those three first. map-keys would basically just be map with (keys coll), map-values would be with (vals coll) and entries would just be a normal map, as hashmaps in Clojure are seqs and mapping over hashmap returns entries as [key val]. A simple destructuring in your map function and you're good to go. Don't quite understand what group-by-1 and group-by-2 does though. Elaborate?
- guard-of-terra 12y agoThe need of repeating exactly the same destructuring or transformation (keys, uncurrying tuple) constaints your thought. You need to have an instrument that fits your domain perfectly. group-by-[12] groups a [[k, v]] list by either k or v, of course you can do this with group-by first, but see above.
- coldtea 12y ago>You need to have an instrument that fits your domain perfectly. Well, then you have to built it FOR your domain. It would strange to have the built-in functions fit ANY domain.
- guard-of-terra 12y agoMap-reduce enthusiasts were wrong: the two fundamental functions are not map and reduce, rather zip-with and group-by. If you want to do real work done. I've heard Google learned this lesson and their data processing now rests upon these two.
- adambard 12y ago`zip-with` and `group-by` just represent the most common uses of `map` and `reduce` respectively. Many data processing frameworks (cascading, spark, storm, etc.) build these abstractions right on top of map-reduce, and sometimes the layer is pretty thin. Heck, zip-with in clojure is literally just map: (map + [1 1 2 3] [1 2 3 5]) ; => [2 3 5 8]
- guard-of-terra 12y agoIt's not zip-with that you wrote. You illustrate that given not enough tools, you lose the grip on the process. zip-with is (zip-with #(+ % 1) [1 1 2 3]) ; => [[1 2] [1 2] [2 3] [3 4]]
- Guthur 12y agoAm I missing something? From my understanding ZIP should be over more than one sequence, otherwise it's just a normal map really. Certainly the Haskell examples I have seen would strongly reinforce my understanding.
- Guthur 12y agoDown voted? Really come on.
- Guthur 12y agoLets race all the way down, seems like it's a meaningless metric anyway.
- guard-of-terra 12y agoWell, it zips a sequence with another derived sequence.
- darklajid 12y agoLike (defn zip-with [f xs] (map (juxt identity f) xs)) or something else? Without showing what your zip-with (and all the other 'missing' things) implementation looks like, people need to guess what they are and why these might be useful.
- darklajid 12y agoIt seems you had to explain all but the first three in this thread. That suggests to me that these are potentially nice shortcuts for you personally, but they might be more confusing than the well-defined standard building blocks you try to hide in shared code..
- praptak 12y agoYou'll quickly get the coolest functions if you do the problems on 4clojure.com. Checking solutions of the best users after my own was enlightening. The typical compression ratio is a 7 lines nested monster with loops to a one line snippet :) By the way, after 4clojure 'apply' would be within my top 10. It might not look like much (man, I can call my fns directly!) but the fact it can prepend individual arguments to the sequence makes it a very useful tool: (apply mapv vector m) transposes m (assuming vector of vectors). Cool, huh? My own transpose would not be that terse :)
- vanderZwan 12y agoUnless there's a connection to foreclosures that I'm unaware of, I think you meant: http://www.4clojure.com/ http://www.4clojure.com/ But despite that silly typo, thanks for the link!
- praptak 12y agoCorrected, thanks!
- aaronem 12y agoHe might want to see to his stylesheet: http://i.imgur.com/KmKlAce.jpg http://i.imgur.com/KmKlAce.jpg
- bad_user 12y agoPersonally I do not understand the utility of "pmap" - seems interesting, but in practice there are really few scenarios in which it is actually useful. And btw, the given example is extremely awful. Yes I understand it's trying to simulate a long running computation, but that's the problem - if you want to parallelize long running computation, then pmap is not what you want.
- jezclaremurugan 12y agoI frequently use Pool's map in python to achieve the same, so can you elaborate a bit on why that's not what is needed?
- _pmf_ 12y agoI've used it for lazy on-the-fly image scaling transformations (desktop client software), but I agree that it's not really of general interest.
- dj-wonk 12y agoI agree that the given example is not compelling. > "but in practice there are really few scenarios in which it is actually useful" Not true. Parallel mapping is a useful tool for multi-core systems. Clojure (and other languages) include a `pmap` because it provides a simple, elegant way to do parallel computation. If you want parallel mapping with more control, check out Claypoole: > Basically, we just wanted a better pmap. Clojure’s pmap is pretty awesome, but we wanted to be able to control the number of threads we were using, and it was nice to get a few other bonus features. http://eng.climate.com/2014/02/25/claypoole-threadpool-tools-for-clojure/ http://eng.climate.com/2014/02/25/claypoole-threadpool-tools... HN commentary: https://news.ycombinator.com/item?id=7299309 https://news.ycombinator.com/item?id=7299309
- Anderkent 12y ago> empty > Not to be confused with empty?, which returns whether or not a collection is empty, empty returns an empty collection that is the same type as its argument: No, actually, it returns an empty collection that is the same type as its argument OR NIL. The 'or nil' is really important, and what makes `empty` really not that useful. Say, for example, that I wanted to iterate over a generic collection, and modify each element recursively, while preserving the types. One would think this would work: (defn map-recurse [coll f] (if-not (coll? coll) (f coll) (into (empty coll) (map #(map-recurse % f) coll))) Alas. You'll find that while maps are in theory lists of vector pair (empty (first {:a 1})) => nil So empty really isn't very useful at all. If you know the collection will be one of the types that it supports, you might as well just create the empty collection explicitly - you know what type it is, after all.
- gregschlom 12y ago> You'll find that while maps are in theory lists of vector pair They aren't. When you seq over a map you get a list of MapEntry, like so: (clojure.lang.MapEntry. :a 1) MapEntries aren't vectors, and while they can be destructured like vectors, you can't make empty instances of them. (empty) still is useful in a variety of cases. See for example this bug report that I've filled: http://dev.clojure.org/jira/browse/CLJ-1476 http://dev.clojure.org/jira/browse/CLJ-1476
- Anderkent 12y agouser=> (pr-str (seq {:a 1 :b 2})) "([:a 1] [:b 2])" user=> (vector? (first (seq {:a 1 :b 2}))) true It looks like a vector, it quacks like a vector...
- eddieplan9 12y agouser=> (doc vector?) ------------------------- clojure.core/vector? ([x]) Return true if x implements IPersistentVector nil So `MapEntry` implements `IPersistentVector`, but is not a vector. It does not make sense to have an empty `MapEntry`, so IMHO the behavior implemented is valid.
- aredington 12y agoKeep in mind that read-string, eval, and load-string are all exploit vectors. Read-string exposes you to the #= eval macro, which can execute arbitrary code at read time, as well as various vulnerabilities with object construction. Eval executes the data it receives without any sandboxing or sanity checking, any data that has ever had any user influence should be suspect. 9 times out of 10, if you're using eval, you should be using a function reference. Load-string is likewise vulnerable to the eval macro and constructor attack vectors. clojure.edn offers safe versions of read and read-string that do not use the eval macro. You can get load-string equivalent behavior by wrapping the string in a StringReader and then clojure.edn/reading it until eof. If you're reading data that's had any influence from an end user, always use the clojure.edn facilities, and never eval it.
- dj-wonk 12y agoThe clojure.walk functions are sometimes overlooked and very powerful: http://clojuredocs.org/clojure_core/clojure.walk http://clojuredocs.org/clojure_core/clojure.walk Take a look at postwalk: "Performs a depth-first, post-order traversal of form. Calls f on each sub-form, uses f's return value in place of the original. Recognizes all Clojure data structures. Consumes seqs as with doall."
- dj-wonk 12y agoIn my experience with code that does data ingest and manipulation, assoc-in and update-in are probably the most useful functions. Why? As you get used to functional programming, you look for ways to reach in and 'update'* nested data structures. Since you don't have object dot notation, you need another way. * By 'update' I mean to return a new persistent data structure with a small change.
- islon 12y agoHe forgot `group-by` which is very useful. (group-by :age [{:name "joe" :age 13 :sex \m} {:name "sara" :age 13 :sex \f} {:name "John" :age 20 :sex \m}]) => {13 [{:age 13, :sex \m, :name "joe"} {:age 13, :sex \f, :name "sara"}], 20 [{:age 20, :sex \m, :name "John"}]}
- roryokane 12y agoI flagged this story, to point out to the moderators that the title should be changed to “Cool Clojure functions” (it is currently “20 cool Clojure functions”).