In Clojure there's the classic way, with condp and mod.— Alexandre Grison (@algrison) May 25, 2018
There's also another way using cycle that I saw some years ago. The range and the 2 cycles will generate the fizz & buzz, the rest just decides what to print.
Will be easier for you with syntax highlighting -> screenshots pic.twitter.com/wOPJD0BpGM
The code on the left side is the following:
(defn div-by? [n d] (zero? (mod n d))) (defn fizz-buzz [n] (condp = [(div-by? n 3) (div-by? n 5)] [true true] "FizzBuzz" [true false] "Fizz" [false true] "Buzz" (str n))) (->> (range 1 100) (map (comp println fizz-buzz)))
I don’t know anything about Clojure, but only it’s a functional language. I think it’s an interesting exercise to try to make sense of the above snippet, especially since it’s quite limited in size. In this post, I’ll adopt the perspective of a Java developer.
Parentheses, parentheses everywhere
If you’re familiar with the family of C languages only (like myself), the snippet looks quite obscure. The first thing to start with is to know that Clojure belongs to the Lisp family of languages. The latter enforce parentheses around every expression, statement, call, etc.:
(str n) ; returns n.toString()
No static type checking
Clojure has no static type checking. None. Zero. Zilch. You can check the absence of types in the solution above.
|If one is really uncomfortable about that, there’s a subproject bringing types to Clojure.|
No return keyword
Clojure is a functional language. As such, it does its best to enforce the writing of functions:
A function is a process or a relation that associates each element x of a set X, the domain of the function, to a single element y of another set Y.
That means every function is supposed to return. Hence, the
return keyword is implicit, as seen from the above snippet.
Functions that are not supposed to return a value - impure functions e.g.
Clojure requires writing first the method name, and then arguments (if required). This is pretty similar to Java, but Clojure applies that pattern everywhere, even for arithmetics:
(mod n d) ; returns the reminder of n divided by d
- Clojure identifiers use hyphens, instead of using camel case as would be in Java
(div-by? n 3) ; calls the div-by? function with arguments n and 3
- Function returning a
booleanvalue should end with
defn macro allows to define a new function. Arguments of the macro are:
- The function name
- The list of arguments, specified as an optionally-empty array
- The function body
(defn div-by? (1) [n d] (2) (zero? (mod n d))) (3)
|2||Function arguments, wrapped in an array|
Function chaining is achieved through the
(def times-inc (comp inc *)) (1) (times-inc 2 3) (2)
|1||Composes functions of multiply and increment by one|
|2||Calls the defined function with arguments |
condp macro replaces the traditional
switch statement, but is more powerful. It’s more similar to the pattern matching feature found in Kotlin and Scala.
(condp = [(div-by? n 3) (div-by? n 5)] ; a two-elements array of boolean [true true] "FizzBuzz" [true false] "Fizz" [false true] "Buzz" (str n)) ; if no other match, this is the default
To sum up, here are Java equivalents of the above code snippets (or Kotlin when Java is not enough):
There are still some subtle points that I need to dig deeper in:
- The difference between a macro and a function
- The difference between
Afterwards, potential next steps include:
- Decode the code displayed on the right part of the original tweet
- Dive into collections
- Learn about Clojure/Java interoperability
- Try to develop a basic Spring Boot application
- Check what are the equivalents of Java frameworks for a "classical" stack e.g. web development, persistence, logging, build, etc.