
20 cool Clojure functions - pps
http://daveyarwood.github.io/2014/07/30/20-cool-clojure-functions/
======
praptak
You'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
Unless 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
Corrected, thanks!

------
Anderkent
> 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
> 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

        user=> (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

        user=> (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.

~~~
Anderkent
'Hey, if I peek under the abstraction, it all makes sense'. Obviously. The
code does what it's written to do. That doesn't make such leaky abstractions
okay. `vector?` is, AFAIK, the way to check if something is a vector, and if
empty doesn't work on some `vector?`s, then i can't trust it.

empty mapentry returning nil is meh. If it returned an empty vector it'd be
what you want 99% of the time.

------
aredington
Keep 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
The 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."

------
islon
He 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"}]}

------
bad_user
Personally 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.

~~~
dj-wonk
I 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...](http://eng.climate.com/2014/02/25/claypoole-threadpool-tools-for-
clojure/)

HN commentary:
[https://news.ycombinator.com/item?id=7299309](https://news.ycombinator.com/item?id=7299309)

------
dj-wonk
In 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.

------
guard-of-terra
May 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
Don'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
The 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
> _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.

------
roryokane
I 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”).

------
aaronem
He might want to see to his stylesheet:
[http://i.imgur.com/KmKlAce.jpg](http://i.imgur.com/KmKlAce.jpg)

