Clojure by Example (2015)
kimh.github.io
kimh.github.io
But the trade off is you short change yourself on learning core philosophical beliefs underpinning the design of the language. Understanding these poorly means you will not exploit the language very well and arguably should just stick with the prior language.
Clojure more than most languages is built around some unconventional philosophical beliefs. So for example “reduce” becomes more than just another sequence function; in clojure it is an important way to build up a result in the absence of mutable variables (which in another language you could use to build up a result within an arbitrary type of loop). You really don’t get that from reading this cheat sheet. But if you took some time to read a slower introduction this will be made clear (“reduce” is prominent in Clojure for the Brave and True for example).
Similarly recursion becomes much more important in clojure due this same constraint.
As a lisp, clojure offers the opportunity to use macros, which come with tremendous power but also some important gotchas. You won’t get much here on when to use and not use macros, just a quick walkthrough of defmacro and friends.
I could go on. Clojure’s powerful concurrency primitives, its a-la-carte approach to polymorphism and inheritance, its emphasis on plain maps — all these types of things warrant a deeper rethinking of how you solve programming problems which you’re not going to get by just diving in (rushing in really) to some coding.
I’m not trying to knock this guide, it’s fine, but it does show the limits of the very approach it celebrates. It’s fine if you’re jumping from say python to ruby or Java to C# but Clojure would be near the bottom of the list of languages to learn this way.
Guides like this give a quick-start into that world. The book-driven big-philosophy route might work better for some people, but to take your first example of "reduce" being special - well that's never really occurred to me as important or remarkable.
In time Clojure has transformed how I program computers, so the big ideas have settled in. I've never finished a Clojure book so I can't offer much reportage on their effectiveness.
We all learn differently, I guess that's the point.
I’d second the person who suggested watching some Rich Hickey talks on YouTube, or at least trying one and seeing if it agrees with you (that is, don’t force it if you’re miserable 10m in but I find him a compelling speaker and others seem to as well).
In descending order of preference I’d suggest (all are on YouTube):
- Effective Programs - 10 years of clojure (great overview of overall thinking behind clojure, touches a lot of key points, and a fun talk)
-Are We There Yet - this one resonated with me but not in most people’s top lists it would seem - a head on critique of OOP and dive into what makes functional and Clojure in particular better
-Simple Made Easy 2011 (there’s a 2012 where the audience is kind of a drag) - one of his most popular, his vision of what simplicity really means and how to achieve it in code
-the value of values - deep dive on clojures data philosophy
https://github.com/gengstrand/clojure-news-feed/blob/master/...
and in python
https://github.com/gengstrand/clojure-news-feed/blob/master/...
and in node.js javascript
https://github.com/gengstrand/clojure-news-feed/blob/master/...
That last one is pretty old and predates async and await.
- Arena Analytics: https://arena.io/
- Reify Health: https://www.reifyhealth.com/
- Guaranteed Rate: https://www.rate.com/
- OppLoans: https://www.opploans.com/
- LookingGlass Cyber Solutions: https://lookingglasscyber.com/
I also believe that DRW (https://drw.com/) also use Clojure but not 100% sure on that. I know they use some Elixir though.
Clojure requires a mental shift but it's not Haskell neither, you can be extremely productive after a few months.
cond-> and cond->> which takes an expression and then threads it through test/form pairs without short circuiting by stopping on the first test that returns true. I have used this a lot when conditionally adding to maps. Something like this:
(let [base-map {:id "123"}
name (function-that-could-return-nil)
address (another-function-that-could-return-nil)]
(cond-> base-map
(some? name) (assoc :name name)
(some? address) (assoc :address address)))
If name and address are nil then I will still have the base-map.
I also use some-> and some->> a lot as well. some-> and some->> operate similar to -> and ->> respectively but the difference is that there is a nil check before subsequent form. It will prevent nil results to keep being passed and can help alleviate NullPointerExceptions.
As for maps, I recommend adding assoc-in, dissoc, update and update-in. assoc-in allows adding nesting to a map. I could do (assoc-in {} [:base :value] "test") which would result in a map that looks like {:base {:value "test"}}. dissoc removes a key from a map. update allows you to "update" a map by passing a key and function where the function has an argument that is the old argument. An example of that is (update {:name "James" :age 26} :age inc) which will increase the value of :age by 1. update-in allows you to do the same as update but in a nested structure (merge base-map
(when name
{:name name}
(when address
{:address address})
Or to avoid repeating field names, just form one map to be merged like {:name (function-that-may-return nil) :address (function-2)} and filter out nils from the map before merging that with-base-map.I guess with cond-> you have the advantage that you can do arbitrary map transformations, not just additions, so maybe this is just a case of nitpicking a strawman example - but doing only additions is a common case too.
filter takes a predicate and a collection and will return a lazy sequence of all items that the predicate returns logically true. For example:
(filter odd? [1 2 3 4])
would return a lazy sequence of (1 3)keep is a function that I was unaware of (and most of my team was too) until I was turned on to it by a former coworker. keep takes a function and a collection and returns a lazy sequence of non-nil results. For instance, if I have a collection of maps and I want to only get a value of a specific key I could do the following with map and remove
(def items [{:v "hi"} {:v "there"} {:m "not v"}])
(remove nil? (map :v items))
What the map does is get the key `v` from each map in the collection and we end up with a sequence that looks like `("hi" "there" nil)` Since the last map in the collection `items` does not have a key `v` the value will return nil. We would then need to use the remove function with the nil? predicate which will remove all items in the collection that are nil. If we use keep, though, we could do this in one pass. (def items [{:v "hi"} {:v "there"} {:m "not v"}])
(keep :v items)
Because keep already removes nil for us we end up with `("hi" "there")`What's missing is current complete examples with the library choices and configuration all worked out.
They may not be complete enough for your tastes?
I definitely can sympathise with the ecosystem being complex to get into =)...
Take Fathom which I walk through a small fraction of in [0] and [1], there's a lot there. It's being actively worked on and there's a lot of docs to go through. That said Clojure's communities are pretty friendly, you can get pretty far by just finding some example projects, tinkering with them and asking questions =)...
- [0]: https://folcon.github.io/post/fulcro-basics/2020-05-12-Fulcr...
- [1]: https://folcon.github.io/post/fulcro-basics/2020-05-26-Sprin...
- [2]: https://luminusweb.com/docs/guestbook
- [3]: https://purelyfunctional.tv/guide/re-frame-building-blocks/
It does actually:
(Math/pow 2 3)
;;=> 8If you instead used the library org.clojure/math.numeric-tower [1] you can get much more precise answers, for example
# start a repl with an extra dep
$ clj -Sdeps '{:deps {org.clojure/math.numeric-tower {:mvn/version "0.0.4"}}}'
user=> (require 'clojure.math.numeric-tower)
nil
user=> (Math/pow 33 33)
1.2911004008776103E50
user=> (type *1)
java.lang.Double
user=> (clojure.math.numeric-tower/expt 33 33)
129110040087761027839616029934664535539337183380513N
user=> (type *1)
clojure.lang.BigInt
[1] https://github.com/clojure/math.numeric-towerHum, ok, to my defense it's not the very next sentence, it's much later at the very end when talking about Method invocation, so I hadn't read that far.
+1 for linking to the numeric tower though
No need to pull the library for this, Clojure already has (biginteger) for that:
user=> (.pow (biginteger 33) 33)
129110040087761027839616029934664535539337183380513The nice ergonomic thing about clojure.math.numeric-tower is you can shove any of the Clojure numeric types at it (longs, doubles, but also Ratio or BigNum etc) and it'll intelligently handle the various combinations of types in order to give you the best answer it can
user=> (bigint "129110040087761027839616029934664535539337183380513")
129110040087761027839616029934664535539337183380513N
user=> (type *1)
clojure.lang.BigInt
The Java-platform bigint is java.lang.BigInteger which are created with (biginteger) which was added in Clojure 1.0. Printing one of these will not include an N suffix: user=> (biginteger "129110040087761027839616029934664535539337183380513")
129110040087761027839616029934664535539337183380513
user=> (type *1)
java.math.BigInteger
Clojure BigInts are supposed to involve less unboxing and therefore be more performant. If you provide an integer literal that's too large to fit in a java.lang.Long, the reader will use a BigInt instead: user=> 129110040087761027839616029934664535539337183380513
129110040087761027839616029934664535539337183380513N
user=> (type *1)
clojure.lang.BigInt
user=> 12345
12345
user=> (type *1)
java.lang.Long
If all this seems like a mess, well it sort of is. Clojure has a very strong commitment to backwards compatibility, so when the Clojure-specific big int representation was added, the old JDK-based one was not removed, because the operations that the two support are different. (map (partial Math/pow 2) [0 1 2 3])This will work: (map (partial #(Math/pow %1 %2) 2) [0 1 2 3])
/pedantic
If your point is that it’s wrong in spirit, I totally agree, and I think this is yet another example of the limits of this “cheat sheet” approach. If you’re coming from another language, the concept of easy, well integrated interop with a host language platform would be fairly foreign, so the importance of an available Java method, and the fact that such methods are frequently used as core parts of the language, would be totally lost on you.
And this is then consistent in ClojureScript as well.
Of most of the books written on clojure i really like "Living Clojure" by Carin Meier. Throughout this book, she will give you the feeling that she is sitting beside you and you pair programming with a senior clojure developer. She goes very elegantly into each topic through actual examples and necessary commentary.
Highly recommend this book :- https://www.oreilly.com/library/view/living-clojure/97814919...
Virtually every O'Reilly book starts out with small (and sometimes dirty) code examples.
Show HN: Clojure by Example - https://news.ycombinator.com/item?id=9692281 - June 2015 (35 comments)