- Published on
Some Clojure Basics
- Authors
- Name
- Yair Mark
- @yairmark
As discussed in a previous post I setup Clojure on my machine with the aim of using it to solve the problems in the 2018 Advent of Code challenge. My aim of doing this is to get a feel for the Clojure language.
It is quite a shift from other languages I have worked with. It is certainly more terse but with that comes a steeper learning curve. I finished the day one challenge. The code for this can be seen below:
(require '[clojure.string :as str])
(def lines (str/split (slurp "./resources/day1.txt") #"\n"))
(defn as-signed-long [strnum]
(let [[sign & rest] strnum]
(def num (read-string (str/join rest)))
(if (= sign \-) (* -1 num) num)))
(defn day1 []
(reduce + (map as-signed-long lines)))
(print (day1))
Based on my attempt and eventual solution to the problem I learnt the following things about the language.
Creating a Project
This is done using lein new app your-project-name
In the above app
is a lein template. Templates are modules that can be installed using lein plugin install plugin-name
. A list of plugins can be found here.
All Functions Return A Value In Clojure
You do not and cannot specify a return value. It seems (based on my initial experiences with the language) that whatever is on the last line is returned:
(defn say-hello [name]
(str "Hello " name "!"))
(say-hello "John")
> Hello John!
If the function runs a command that does not return something then nil
is returned:
(defn func-that-printlns [foo]
(println foo))
(def res (func-that-printlns "Blah"))
(type res)
> nil
(print res)
> nilnil
Working with Characters
Clojure does have the concept of characters i.e. individual letters or numbers. But despite it being a JVM language you refer to these characters using different syntax. For example if you want to refer to the single character a
, in most languages you would do so as 'a'
in Clojure you instead use a back slash: \a
. For example to check if a character variable foo
is equal to the -
character:
(= foo \-)
> true
String Destructuring
- This is done using let together with
apply
whereapply
is used to work on the destructured parts. - You can return the result of the let parameter as the last result in your function. For example in the advent of code 2018 day 1 challenge to convert a string number with a sign e.g.
"-122321"
to a signed number:
(defn as-parts [strnum]
(let [[sign & rest] strnum]
(def num (read-string (str/join rest)))
(if (= sign \-) (* -1 num) num)))
Determining The Data Type of Variables
I found the easiest way to work on a solution to a problem was to play around with the problem in the lein repl
. In doing so it is not always clear what the types are of the variables you are working with. To this end you can use the type
command:
(type some-variable)
> java.lang.String
This is very useful in determining if you need to transform the given variable or/and what operations are available on it.
Loading Files from the Classpath
Coming from a JVM background came in handy here when trying to grok how to read files in a Clojure file. You refer to the root of the classpath using ./
.
For example if I have the following directory structure:
projectRoot
-> src
-> your
-> package
-> someFile.clj
-> resources
-> some-package
-> someprops.txt
You would load someFile.clj
in the lein repl
as:
(load-file "./src/your/package/someFile.clj")
The above assumes you are running the repl from inside a project directory anywhere in the project.
You can also run the file using cat someFile.clj | lein repl
but this is much slower as it has to startup the repl first.
To read a file in a .clj
for example if you are processing a text file you can use the slurp
command which I found to be the most concise way to do this. For example say I want to load a file someprops.txt
I would do this as:
(slurp "./resources/some-package/someprops.txt")
Various Tips and Gotchas
Tips
- Code Formatter: Having some sort of code formatter in your editor of choice helps a fortune in working out if there are brackets missing or some too many.
- Comments: are as simple as
(comment stuff-you-want-to-comment)
Gotchas
- Joining Sets: You cannot
conj
sets together you have to useclojure.set/union
(def set1 #{1 2 3})
(def set2 (clojure.set/union set1 #{1 3 4 5 6}))
(print set2)
> #{1 4 6 3 2 5}
- Iterating Through Lists Concisely: Iterating through a list is a bit more tricky (at least to someone new to clojure) than it would seem as there are a number of different ways to do this. The most succinct way I found to do this (in my brief search) was:
(def some-list [1 3 5 6])
(doseq [num some-list] (print num))