Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Clojure by Example (2015) (kimh.github.io)
173 points by manjana on May 26, 2021 | hide | past | favorite | 45 comments


Learning a new language by mapping constructs from another language you know better is certainly expedient.

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.


In my experience I found the ability to produce a similar functional deliverable (e.g. a webservice or similar) in Clojure as I was able to in my previous language (Java) was the most important factor in my learning and continuing with Clojure.

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.


Are there any other resources that you'd recommend to people coming from languages like Python or JavaScript? I'm assuming 'Clojure for the Brave and True'?


Yes, I came from python and ruby and that book helped me a lot. You can read or free on the website, I also bought the Apple Books version.

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


I second the Brave and True book to get your feet wet and add this website to get a better grasp on where/what/how to use the different elements of Clojure, with examples. Especially the "see also" section which will point you to functions you may not have heard of otherwise.

https://clojuredocs.org/


I maintain a github repo where I implement the same feature identical rudimentary news feed microservice in different programming languages and frameworks. Here is the social broadcast function in clojure.

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.


Rich Hickey has a good number of talks on YouTube that explain the thinking behind Clojure's language design


At work we would give new hires "Living Clojure". There is also the site https://www.4clojure.com/ if you are looking to do some exercises.


If you're comfortable sharing, where do you work that uses Clojure? I'm still a bit junior myself (Java dev), but I've been interested in FP and especially LISP for a while


I'm sorry but I am not comfortable sharing, however, here is a list of companies that I know use Clojure.

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


Programming Clojure (The Pragmatic Programmers), will get you there. Also Clojure Programing (O'Reilly) is also good, a bit longer.

Clojure requires a mental shift but it's not Haskell neither, you can be extremely productive after a few months.


If you're more of a learning by doing person, you can try to solve the problems in the Clojure track at exercism.io and compare your solutions with highly rated solutions of others.


Found walking through exercises of Clojure Koans useful


There are a few things I would like to see added as I have found them very useful in day to day work with Clojure(Script).

  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


I see this kind of map update code usually written using merge, like this (ignoring DRY itch for now)

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


Yea I used to do the merge and when as well. I'm not sure if there is an idiomatic way to do it but I personally prefer the threading style as it is "prettier".


Another two functions that I forgot to mention last night are filter and keep. I am actually surprise filter isn't already on the site.

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")`


I love the content, but I hate when pages hijack my back button by placing page headings in my history as I scroll.


The language isn't what's difficult to grasp or what needs more explanation.

What's missing is current complete examples with the library choices and configuration all worked out.


I wholeheartedly agree. There are so many different ways to set up a project in Clojure that it can be a little overwhelming. There is boot, leiningen and deps.edn for managing dependencies as well as project structure. Then with ClojureScript there is shadow-cljs as well as improvements made with ClojureScript embracing the Javascript tooling by handing off the output of the compiler to a JS bundler.


I'm not sure if this is enough, but I wrote these [0][1] a while ago and if they're not to your taste, I do admit to a fairly offbeat writing style, there's also Luminus[2] and re-frame[3].

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/


Personally I've had great success working slowly with an open babashka repl through the tutorials from aphyr: https://aphyr.com/tags/Clojure-from-the-ground-up - everything else didn't really stick for me, but I probably didn't invest enough time. Especially the first few articles helped a lot to clear up misconceptions for a newbie to lisp. I remember reading the linked article but it didn't worked for me.


The aphyr series I think is the best one for a quick intro to Clojure.


> Clojure doesn't provide built-in function for exponential operation

It does actually:

    (Math/pow 2 3)
    ;;=> 8


The very next sentence of the article points out that you can leverage Clojure's interop to invoke Math.pow(), but that's still limited to casting its arguments down to java.lang.Double and suffering the limits therein

If 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-tower


> The very next sentence of the article points out that you can leverage Clojure's interop to invoke Math.pow()

Hum, 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


My apologies. I searched for Math/pow, saw that the sentence there started with "Clojure doesn't", and missed that we were looking at two different parts of the document :)


You need to be familiar with Java types and differences between them (Integer, BigInteger) and how Math.pow() behaves with different types. This is more up to Java, rather than Clojure.

No need to pull the library for this, Clojure already has (biginteger) for that:

    user=> (.pow (biginteger 33) 33)
    129110040087761027839616029934664535539337183380513


Yeah if you know you're definitely dealing with integers that are large, and are willing to pay the cost to promote into biginteger territory, that's certainly a way to do it with the stdlib.

The 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


Didn't Clojure use the N suffix for big integers at one point? I don't see one on that output.


The N suffix is for clojure.lang.BigInt, which are created with (bigint) which was added in Clojure 1.3 and still work to this day (I did these examples in the latest 1.10.3 release)

  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.


`Math/pow` is not a function. If it were, you would be able to do things like this:

    (map (partial Math/pow 2) [0 1 2 3])


"Math/pow" is actually a function on JVM level. Clojure functions are objects with special callable method.

This will work: (map (partial #(Math/pow %1 %2) 2) [0 1 2 3])


“Math” here is short for “java.lang.Math” though. Technically that’s interop rather than Clojure per se.

/pedantic


If you remove the Java SDK from Clojure's foundation, you're losing a lot of its power. The interop is part of that foundation.


For sure, but “Clojure doesn't provide built-in function for exponential operation” is still an objectively true assertion.

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.


Think it might just be quibbling on the nature of 'built-in' at that point.


Well yes hence the “/pedantic” tag. I was quibbling with someone who went out of their way to “correct” a true fact. Quibbling with quibbling if you will. Sort of like you’re doing. But hey they started it ;)


I think for Math/ it's a bit more fuzzy, because it doesn't need to be aliased explicitly, it's always available to use as it is automatically imported as Math.

And this is then consistent in ClojureScript as well.


This is my way of learning too. I certainly dont have a patience to read the docs, official guide before i start. Just hacking directly through repl and getting to know primitive data types, constructs and most commonly needed APIs functions is the most practical way IMHO.

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


> I don't like reading thick O'Reilly books when I start learning new programming languages. Rather, I like starting by writing small and dirty code.

Virtually every O'Reilly book starts out with small (and sometimes dirty) code examples.


One past thread:

Show HN: Clojure by Example - https://news.ycombinator.com/item?id=9692281 - June 2015 (35 comments)


Another clojure tutorial is (*) by Mark Volkmann

http://java.ociweb.com/mark/clojure/article.html


Nice short examples in simple language. Scrolling down does pollute the browser history though.


Quite good indeed.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: