Recently, I had some some fun writing functional Kotlin to solve the FizzBuzz test. I asked for some feedback, and one of the answer I received was in Clojure:
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.
https://en.wikipedia.org/wiki/Function_(mathematics)
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. println
return nil
anyway.
Prefix notation
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
Coding conventions
- 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
boolean
value should end with?
Defining functions
The 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)
1 | Function name |
2 | Function arguments, wrapped in an array |
3 | Function body |
Chaining functions
Function chaining is achieved through the comp
macro:
(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 2 and 3 , returning 7 |
Switch
The 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
Summary
To sum up, here are Java equivalents of the above code snippets (or Kotlin when Java is not enough):
Clojure | Java/Kotlin |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
TODO
There are still some subtle points that I need to dig deeper in:
- The difference between a macro and a function
- The
→>
macro - The difference between
def
anddefn
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.