332 lines
14 KiB
Org Mode
332 lines
14 KiB
Org Mode
#+TITLE: Getting started with Clojure some of the basics.
|
|
|
|
* Clojure basics
|
|
|
|
Clojure is a very dynamic and interactive language that can give you very in-depth feedback on your running programs. For example, you can use Clojure to:
|
|
|
|
- Debug your code more easily by seeing the values of variables, the call stack, and the execution time of each function.
|
|
- Experiment with different ideas more quickly by evaluating code live inside an IDE.
|
|
- Use third party graphical tools to aid debugging and testing your code like portal, portfolio & clerk
|
|
|
|
The code below can be evaluated live inside the page you can select parts of the code to evaluate partially you can also modify the code the new version will be evaluated live.
|
|
|
|
|
|
#+BEGIN_SRC edn :results silent :tangle deps.edn
|
|
{:paths ["src" "resources"]
|
|
:deps
|
|
{org.clojure/clojure {:mvn/version "1.10.0"}
|
|
org.clojure/clojurescript {:mvn/version "1.11.60"}}}
|
|
#+END_SRC
|
|
|
|
|
|
#+BEGIN_SRC text :results silent :tangle readme.org
|
|
#+TITLE: Getting started
|
|
|
|
* Install the npm requirements.
|
|
|
|
npx install
|
|
|
|
* Launch shadow-cljs watch for source code changes
|
|
npx shadow-cljs watch app
|
|
#+END_SRC
|
|
|
|
|
|
** Clojure comments
|
|
|
|
There a few ways to comment code in this language.
|
|
|
|
- Semi colon's can be used to comment out a whole line handy for adding in detailed comments, for commenting out code you can also use semi colons but more useful is the #_ syntax.
|
|
- #_ prefixed before a bracket will comments out everything between the opening and closing bracket, this is most hopefull to comment out entire blocks in one go.
|
|
- Another method is to use rich comments which is a function which stops the evaluation of everything inside the block, this is extremely useful as you can run the code from your IDE but it will not be executed in production.
|
|
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
; comments are semi colon, linters will treat alignment different bassed on the number of semi colons
|
|
|
|
; comment out the block of code from starting bracket to matching closing bracket
|
|
; in this example the whole if condition is commented out which spans 3 lines
|
|
#_(if true
|
|
(prn "true")
|
|
(prn "false"))
|
|
|
|
; Ignore this code, but we can still eval it inside an IDE
|
|
(comment
|
|
(+ 1 2 3))
|
|
|
|
#+END_SRC
|
|
|
|
** Basic datatype's
|
|
|
|
Clojure has a rich set of data types, including numbers, strings, characters, booleans, and nil.
|
|
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
;; comments are semi colon, linters will treat alignment different bassed on the number of semi colons
|
|
;;Integers
|
|
1234
|
|
;;Doubles
|
|
1.234
|
|
;;BigDecimal
|
|
2.33M
|
|
;;Ratios are allowed
|
|
34/3
|
|
;;Strings are double quoted
|
|
"Hello World"
|
|
;;characters are backslash escaped
|
|
\a \b \c \e \t \c
|
|
;;Symbols (named things like function & variable) are just text
|
|
;;these are quoted with a ' to stop evaluation, we have not actually defined them yet
|
|
;;this stops evaluation
|
|
'my-var-name 'my-fn-name 'my-second-var-etc 'my-second-fn-etc
|
|
;;hash map or dictonary keys are prefixed with a colon, no need to quote
|
|
:key1 :key2 :key3
|
|
;;booleans
|
|
true false
|
|
;; Null or None is just nil
|
|
nil
|
|
;; Regex patterns are strings prefixed with a hash symbol
|
|
(clojure.string/replace "find & replace in a string" #"&" "and" )
|
|
#+END_SRC
|
|
|
|
** Collections of data
|
|
|
|
Clojure also has a number of compound data types, such as lists, vectors, maps, and sets.
|
|
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
;; List are wrapped in brackets, space or comma act as seerators
|
|
;; Lists are special in that they need to be escaped to be treated as data
|
|
;; using the list function or the quote symbol, which is short hand and explained later.
|
|
(list 1 2 3 4 5 6 7 8 9)
|
|
'(1 2 3 4 5 6 7 8 9)
|
|
|
|
;;Vectors or indexed lists are created with square brackets
|
|
;;or using the long form vector function
|
|
[1 2 3 4 5 6 7 8 9]
|
|
(vector 1 2 3 4 5 6 7 8 9)
|
|
|
|
;; hash-maps are denoted with curly braces or calling hash-map function
|
|
;; they must always contain key value pairs.
|
|
{:key-one 1 :key-two 2}
|
|
(hash-map :key-one 1 :key-two 2)
|
|
|
|
;; sets also use curly braces but start with a # or you can call hash-set function
|
|
;; sets have to be unique so duplicates will throw an error
|
|
#{1 2 3}
|
|
(hash-set 1 2 3)
|
|
;;#{1 2 1 3} how ever would through an error, using hash-set would remove the duplicate with out error.
|
|
#+END_SRC
|
|
|
|
* Clojure syntax
|
|
You have basically just learnt the syntax, clojure's syntax is made from the same data structures described above this is meant literally and is called EDN.
|
|
|
|
The clojure language is super simple it is always a function call followed by arguments, this applies for things like standard conditionals =and=, =or=, =not= etc
|
|
|
|
All functions return a value, this is always the last statement called inside the function.
|
|
|
|
When ever you see an opening bracket the value after is always a function unless the bracket is preceded by a character in which case this is a macro a common macro is ='(1 2)= which is the same as typing (list 1 2) so a function is still the first parameter.
|
|
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
;; this code add's two number together, its starts with a bracket and call a function called + with 2 parameters
|
|
(+ 1 1)
|
|
|
|
;; this code now creates a list and does not execute the + function
|
|
;; it now returns 3 items in the list the function and the two numbers
|
|
(list + 1 1)
|
|
#+END_SRC
|
|
|
|
|
|
** Maths
|
|
All maths operators are also functions, meaning you start with the operation then the numbers to apply the operator against, opposed to many other languages where you would separate the numbers by operators.
|
|
|
|
This goes back to function as the first item in a list then the arguments.
|
|
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
;; This is equivalend to 1 + 2 + 3 in other languages
|
|
(+ 1 2 3)
|
|
|
|
;; When using multiple operators just nest them.
|
|
(* 2 (+ 1 2 3))
|
|
#+END_SRC
|
|
|
|
|
|
** Variables
|
|
Variables in clojure are defined with =def= function, how ever unlike other languages you can not change these unless using some construct which allows the value to be changed like an atom.
|
|
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
;; These define variable Which can not change
|
|
(def my-string "A string once set does not change")
|
|
(def my-number 3.14)
|
|
|
|
;; If you want to change your variables you need to define your variable as an atom
|
|
;; Where possible you want to avoid this, the language is immutable by design to help avoid bugs
|
|
;; any variable that can be arbitarily changed like atom's means any piece of code can change the value
|
|
;; and potentially cause issues when used else where in functions that did not expect the new value
|
|
(def my-atom-string (atom "A string once set does not change"))
|
|
(def my-atom-number (atom 3.14))
|
|
(def my-atom-hashmap (atom {:one 1 :two 2}))
|
|
|
|
;;One important thig to remember is that to access the values of an atom you need to prepend an @
|
|
;;when using functions that change an atom you do not need the @
|
|
;;@ is shorthand for the deref function
|
|
@my-atom-string
|
|
@my-atom-number
|
|
@my-atom-hashmap
|
|
|
|
;; Atoms are changed by calling functions which change the value in a thread safe fashion
|
|
;; reset! is to replace the value, swap! is used to update a value and takes a function to apply the update
|
|
(reset! my-atom-string "My new string")
|
|
;; Append to the existing string
|
|
(swap! my-atom-string str " appended text")
|
|
|
|
;; replace with empty hash map
|
|
(reset! my-atom-hashmap {})
|
|
;; add new key and value using assoc function
|
|
(swap! my-atom-hashmap assoc :key-one "value")
|
|
|
|
;; There is also defonce which is handy for hot reloading
|
|
;; when your code is reloaded all variables will be reset to the inital states
|
|
;; with defonce the values are maintained over reloads which helps when testing user flows
|
|
;; by maintining the state the user does not need to start again
|
|
(defonce my-atom-hashmap (atom {:ex-one "hi"}))
|
|
(swap! my-atom-hashmap assoc :key-one "value")
|
|
|
|
#+END_SRC
|
|
|
|
** Conditionals
|
|
Similar rules apply to if conditions =if=, =and=, =or= and =not= are also functions, it's also worth noting that all these function return values in other languages like python you would update a variable and the if would not directly return a value, try evaluating these to get a feel for this.
|
|
|
|
When needing multiple conditions look into, =cond= =condp= or =case=
|
|
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
;; OR AND NOT can be used by there own as function calls
|
|
(or nil 1)
|
|
(and nil 1)
|
|
(not true)
|
|
|
|
;; These can then be mixed with the if condition function like so
|
|
;; its important to note that the first parameter is the condition to check
|
|
;; then the code to call followed by the else function, you can only call a single function
|
|
(if (true? (not true))
|
|
1
|
|
2)
|
|
|
|
;; If you don't need the else your better of using when
|
|
(when (= 1 1)
|
|
(prn "True so print me!")
|
|
(prn "multiple statements aloowed in a when, only last returns a value"))
|
|
|
|
;; If you need to call multiple function when only one is allowed use the do function.
|
|
(if (true? (not true))
|
|
(do
|
|
(prn "first thing todo")
|
|
(prn "second thing todo, last statement will be the return")
|
|
1)
|
|
2)
|
|
#+END_SRC
|
|
|
|
** Looping
|
|
For the most part you will use =map= =filter= and =reduce= which apply a function for each item in a sequence of values, for the most part this is enough.
|
|
|
|
You can also use loop which takes your initial values as params inside the [] block and you call recur supplying the updated values in each iteration, not calling recur will end the loop.
|
|
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
;; set n to 0 initially, increment in each loop while n is less than 5 return n value when this is no longer the case
|
|
(loop [n 0]
|
|
(if (< n 5)
|
|
(recur (inc n)) n))
|
|
#+END_SRC
|
|
|
|
For loops are also available in a similar fashion to =loop=
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
;; loop over the sequence of numbers, assigning each to X until the end
|
|
;; run any functions on the value in the rest of the body
|
|
(for [x [1 2 3 4 5 6]]
|
|
(* x x))
|
|
#+END_SRC
|
|
|
|
|
|
** Hashmap's keyword's & De-structuring
|
|
One of the fundamentals to working with hashmap's in clojure is that keywords are function when ever you see =:my-key= you are actually calling a function this is very different to most other languages.
|
|
|
|
This has some really nice side effects, one being it is very easy to navigate your hasahmap's the example below shows how to pull out the value 2.
|
|
|
|
*** Basic value fetching
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
(def my-hashmap {:top-lvl-key {:first-key 1 :second-key 2}})
|
|
|
|
;; you can get the value in a number of ways.
|
|
;; using neted get return the result of one get to the next
|
|
(get (get my-hashmap :top-lvl-key) :second-key)
|
|
;; much nicer is to use get-in and specify the path
|
|
(get-in my-hashmap [:top-lvl-key :second-key])
|
|
;; You can also call the keywords as a function
|
|
(:second-key (:top-lvl-key my-hashmap))
|
|
;; or using something called a threading macro
|
|
;; push the map through the :top-lvl-key function then the result
|
|
;; into the :second-key function
|
|
(-> my-hashmap :top-lvl-key :second-key)
|
|
#+END_SRC
|
|
|
|
|
|
You can use de-structuring in clojure to explode out keys when passed to a function.
|
|
Using =:or= we can set default values if the key is missing, the :as keyword can be used to access the unstructured map.
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
(defn my-fn [{:keys [id name value missing] :or {missing "not set"} :as my-hashmap}]
|
|
(clojure.string/join " - " [id name value missing my-hashmap]))
|
|
|
|
(my-fn {:id 1 :name "bob" :value 456})
|
|
#+END_SRC
|
|
|
|
|
|
* Wierd symbol's
|
|
There is a good reference on the symbols in clojure in the link below when you encounter one your not sure about.
|
|
https://clojure.org/guides/weird_characters
|
|
|
|
Below you will find some simple examples, it is worth noting that some of the wierd symbols are just shorthand for a longer function name calls.
|
|
|
|
=:= Colon indicates a keyword's
|
|
|
|
;; :: Double colon makes a namespaced keyword, what ever is defined at the top of your file under (ns)
|
|
;; will be pre pended to the keyword
|
|
|
|
;; , comma is just white space and is used only for readability to the user
|
|
|
|
;; #( is an annoymous function (fn [param1] (prn param1)) is equivalent to #(prn %) % being param1
|
|
;; you can also use %1 %2 %3 etc to refernce other params, longer form is prefered because the params are named
|
|
;; but for very short small functions this variant can be handy
|
|
|
|
;; -> is the threading macro, basically the result of each statement is passed to the next
|
|
;; as the first parameter, this works nicely with hashmaps because keywords are functions
|
|
|
|
;; ->> same as above but the result is the last parameter
|
|
;; when working with sequences you tend to use this one most sequence functions Take
|
|
;; a sequence as the last parameter
|
|
|
|
;; '( this is the same as writting (list 1 2 3) the ' denotes we are using the list function
|
|
|
|
;; #_ this is the comment block it
|
|
|
|
|
|
#+BEGIN_SRC clojure :results verbatim :tangle src/core.cljc
|
|
;; : Colon indicates a keyword's
|
|
|
|
;; :: Double colon makes a namespaced keyword, what ever is defined at the top of your file under (ns)
|
|
;; will be pre pended to the keyword
|
|
|
|
;; , comma is just white space and is used only for readability to the user
|
|
|
|
;; #( is an annoymous function (fn [param1] (prn param1)) is equivalent to #(prn %) % being param1
|
|
;; you can also use %1 %2 %3 etc to refernce other params, longer form is prefered because the params are named
|
|
;; but for very short small functions this variant can be handy
|
|
|
|
;; -> is the threading macro, basically the result of each statement is passed to the next
|
|
;; as the first parameter, this works nicely with hashmaps because keywords are functions
|
|
|
|
;; ->> same as above but the result is the last parameter
|
|
;; when working with sequences you tend to use this one most functions Take
|
|
;; a sequence as the last parameter
|
|
|
|
;; '( this is the same as writting (list 1 2 3) the ' denotes we are using the list function
|
|
|
|
;; #_ this is the comment block it
|
|
#+END_SRC
|
|
|