├── LICENSE ├── README.md └── materials ├── section-0 ├── README.md └── structure.png ├── section-1 ├── .gitignore ├── README.md ├── project.clj ├── resources │ └── koans.clj ├── script │ ├── bundle.sh │ ├── repl │ ├── repl.bat │ ├── run │ ├── run.bat │ ├── run.clj │ ├── test │ ├── test.bat │ └── test.clj └── src │ └── koans │ ├── 01_equalities.clj │ ├── 02_strings.clj │ ├── 03_lists.clj │ ├── 04_vectors.clj │ ├── 05_sets.clj │ ├── 06_maps.clj │ ├── 07_functions.clj │ ├── 08_conditionals.clj │ ├── 09_higher_order_functions.clj │ ├── 10_atoms.clj │ ├── 11_sequence_comprehensions.clj │ ├── 12_lazy_sequences.clj │ ├── 13_threading.clj │ ├── 14_recursion.clj │ ├── 15_creating_functions.clj │ ├── 16_destructuring.clj │ ├── 17_runtime_polymorphism.clj │ └── 18_refs.clj ├── section-2 └── README.md ├── section-3 └── README.md ├── section-4 └── README.md └── section-5 └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Futurice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clojure Workshop 2 | 3 | [![Sponsored](https://img.shields.io/badge/chilicorn-sponsored-brightgreen.svg?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAMAAADjyg5GAAABqlBMVEUAAAAzmTM3pEn%2FSTGhVSY4ZD43STdOXk5lSGAyhz41iz8xkz2HUCWFFhTFFRUzZDvbIB00Zzoyfj9zlHY0ZzmMfY0ydT0zjj92l3qjeR3dNSkoZp4ykEAzjT8ylUBlgj0yiT0ymECkwKjWqAyjuqcghpUykD%2BUQCKoQyAHb%2BgylkAyl0EynkEzmkA0mUA3mj86oUg7oUo8n0k%2FS%2Bw%2Fo0xBnE5BpU9Br0ZKo1ZLmFZOjEhesGljuzllqW50tH14aS14qm17mX9%2Bx4GAgUCEx02JySqOvpSXvI%2BYvp2orqmpzeGrQh%2Bsr6yssa2ttK6v0bKxMBy01bm4zLu5yry7yb29x77BzMPCxsLEzMXFxsXGx8fI3PLJ08vKysrKy8rL2s3MzczOH8LR0dHW19bX19fZ2dna2trc3Nzd3d3d3t3f39%2FgtZTg4ODi4uLj4%2BPlGxLl5eXm5ubnRzPn5%2Bfo6Ojp6enqfmzq6urr6%2Bvt7e3t7u3uDwvugwbu7u7v6Obv8fDz8%2FP09PT2igP29vb4%2BPj6y376%2Bu%2F7%2Bfv9%2Ff39%2Fv3%2BkAH%2FAwf%2FtwD%2F9wCyh1KfAAAAKXRSTlMABQ4VGykqLjVCTVNgdXuHj5Kaq62vt77ExNPX2%2Bju8vX6%2Bvr7%2FP7%2B%2FiiUMfUAAADTSURBVAjXBcFRTsIwHAfgX%2FtvOyjdYDUsRkFjTIwkPvjiOTyX9%2FAIJt7BF570BopEdHOOstHS%2BX0s439RGwnfuB5gSFOZAgDqjQOBivtGkCc7j%2B2e8XNzefWSu%2BsZUD1QfoTq0y6mZsUSvIkRoGYnHu6Yc63pDCjiSNE2kYLdCUAWVmK4zsxzO%2BQQFxNs5b479NHXopkbWX9U3PAwWAVSY%2FpZf1udQ7rfUpQ1CzurDPpwo16Ff2cMWjuFHX9qCV0Y0Ok4Jvh63IABUNnktl%2B6sgP%2BARIxSrT%2FMhLlAAAAAElFTkSuQmCC)](http://spiceprogram.org/oss-sponsorship) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 5 | 6 | A Clojure workshop intended for Clojure beginners. Participants are not required to have any prior experience with Clojure. 7 | 8 | The workshop materials are intended to guide participants through the whole language and eco system, from theory to deploying an actual web application. The goal of this workshop is to have a person, with no prior knowledge about Clojure, fully capable of writing production ready Clojure after one day. 9 | 10 | The workshop has successfully been organized in: 11 | 12 | - Futurice Helsinki 13 | - Futurice Munich 14 | 15 | ## Prerequisite 16 | 17 | ##### Java 18 | Version 1.8.0 or higher 19 | The command: `java -version` should output: `[...] version "1.8.0_XXX"` 20 | ##### Lein 21 | [Lein](https://leiningen.org/) Version 2.9.1 or higher 22 | The command: `lein -v` should output: `Leiningen 2.9.1 on Java 1.8.0_XXX [...]` 23 | ##### Nightcode 24 | [Nightcode](https://sekao.net/nightcode/) 25 | 26 | ## Workshop set up 27 | 28 | The workshop is split into 6 sections 29 | 30 | 0. Introduction in Clojure 31 | 1. Basic Development - REPL 32 | 2. Backend Programming 33 | 3. Frontend Programming with Clojurescript and Reagent 34 | 4. Database (Extra credit) 35 | 5. Deploying (Extra credit) 36 | 37 | ## License 38 | 39 | ``` 40 | MIT License 41 | 42 | Copyright (c) 2019 Futurice 43 | 44 | Permission is hereby granted, free of charge, to any person obtaining a copy 45 | of this software and associated documentation files (the "Software"), to deal 46 | in the Software without restriction, including without limitation the rights 47 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 48 | copies of the Software, and to permit persons to whom the Software is 49 | furnished to do so, subject to the following conditions: 50 | 51 | The above copyright notice and this permission notice shall be included in all 52 | copies or substantial portions of the Software. 53 | 54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 55 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 56 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 57 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 58 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 59 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 60 | SOFTWARE. 61 | ``` 62 | -------------------------------------------------------------------------------- /materials/section-0/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Introduction 4 | 5 | What is Clojure? 6 | 7 | >Clojure was forged in a mythic volcano by Rich Hickey. Using an alloy of Lisp, functional programming, and a lock of his own epic hair, he crafted a language that’s delightful yet powerful. Its Lisp heritage gives you the power to write code more expressively than is possible in most non-Lisp languages, and its distinct take on functional programming will sharpen your thinking as a programmer. 8 | 9 | \- CLOJURE for the BRAVE and TRUE 10 | 11 | That is a nice and short summary of Clojure. Although, some key things are missing from the description. As the description points out, Clojure is a Lisp, it's also dynamically typed, and it runs on the JVM. In the next section, we'll go through the fundamentals of the language. 12 | 13 | #### Why Clojure? 14 | 15 | Good question! As mentioned above, Clojure is very feature rich. Clojure was built from the ground up to solve concurrency issues and it has a strong connection to Java. Clojure also includes a very powerful REPL which makes testing code quite easy. 16 | A unique feature of Clojure is it's macro system, with which you can extend the capabilities of the language. If you are writing a piece of software that performs a lot of concurrent operations and needs to easily manipulate data structures and it should also be able to run on any platform, then Clojure might be the right choice for you. 17 | 18 | If you're still unsure about Clojure maybe the creator, Rich Hickey, can convince you: 19 | https://www.youtube.com/watch?v=34_L7t7fD_U&t=745s 20 | 21 | ## Table of contents 22 | 23 | 1. [Syntax](#syntax) 24 | 2. [Types](#types) 25 | 3. [Special Forms](#special-forms) 26 | 4. [Examples](#examples) 27 | 5. [Reader](#reader) 28 | 6. [Macros](#macros) 29 | 7. [References](#references) 30 | 31 | ## Syntax 32 | 33 | The syntax, or structure of the language is quite different from what you might be used to. Clojure is written as lists, or *forms*. The form is written as a pair of parenthesis, `()` which is also how you represent a list in Clojure. When you write a basic expression in Clojure, the first thing you will write is a pair of parenthesis, the first thing you write inside the parenthesis will *always be interpreted as a function to call*. The first position inside the parenthesis is called the function position. Anything after that, is interpreted as an argument(s) for the function you are calling. Any Clojure type that implements the `IFn` interface can be placed in the function position. This includes: `keyword`, `vector`, `map`, `set` and of course *functions* and *macros* can also be placed in the function position, and last but not least *special forms* can also be placed in the function position. 34 | 35 | ![Structure](structure.png) 36 | 37 | image from clojure.org 38 | 39 | Above is an example on how to language is structured and evaluated. One key aspect of the language is revealed here, Clojure only has expression, and *all expressions return some value*. Don't worry if this is confusing, we will now go through the basics of the language in detail. 40 | 41 | ## Types 42 | The first fundamental aspect of Clojure, is that all the underlying *types* 43 | are actually plain old Java classes. All of the following types, except for *atoms*, are intrinsically immutable. 44 | 45 | #### Numberic 46 | 47 | ```clojure 48 | (class 1) ;; => java.lang.Long 49 | (class 1.0) ;; => java.lang.Double 50 | (class 1N) ;; => clojure.lang.BigInt 51 | (class 5/2) ;; => clojure.lang.Ratio 52 | (class (short 1)) ;; => java.lang.Short 53 | (class (int 1)) ;; java.lang.Integer 54 | (class (byte 1)) ;; java.lang.Byte 55 | (class (float 1.2)) ;; java.lang.Float 56 | ``` 57 | 58 | Clojure represents all whole numbers as `long`. If you want to use something else, you have to explicitly construct it or cast it. As you can see, numbers are normal Java objects. Clojure does however add two *new* number types, `clojure.lang.Ratio` and `clojure.lang.BigInt`. The more interesting of the two, is the ratio type. 59 | `clojure.lang.Ratio` is any number that can't be represented as an accurate number. 60 | ```clojure 61 | (/ 5 2) ;; => 5/2 62 | (/ 4 2) ;; => 2 63 | ``` 64 | 65 | Interestingly enough, any arithmetic operation that includes a ratio type will always return either a `clojure.lang.Ratio` or `clojure.lang.BigInt` 66 | 67 | ```clojure 68 | (* 1 1/2) ;; => 1/2 69 | (* 2 1/2) ;; => 1N 70 | ``` 71 | 72 | One thing to note is that any ratio number that can be represented as a whole number *it will always be represented as a whole number (long)*. 73 | 74 | ```clojure 75 | 2/1 ;; => 2 76 | (class 2/1) ;; => java.lang.Long 77 | ``` 78 | 79 | #### Strings 80 | 81 | ```clojure 82 | (class "Skadam!") ;; => java.lang.String 83 | (class \v) ;; => java.lang.Character 84 | ``` 85 | 86 | Just like numbers, string and character are both also plain Java objects. 87 | 88 | #### Booleans 89 | 90 | ```clojure 91 | (class true) ;; => java.lang.Boolean 92 | (class false) ;; => java.lang.Boolean 93 | (class nil) ;; => nil 94 | ``` 95 | 96 | As with numbers and strings, booleans are also plain old Java objects. 97 | `nil` however is not a Java object. `nil` is kinda special in Clojure, but let's just pretend that it's the same as Java `null`. If you are interested in learning more about `nil` this article explains it in great detail: https://lispcast.com/nil-punning/ 98 | 99 | #### Collections 100 | 101 | ```clojure 102 | (class '(1 2 3)) ;; => clojure.lang.PersistentList 103 | (class [1 2 3]) ;; => clojure.lang.PersistentVector 104 | (class #{1 2 3}) ;; => clojure.lang.PersistentHashSet 105 | (class {:1 2 :3 4}) ;; => clojure.lang.PersistentArrayMap 106 | ``` 107 | 108 | Clojure has four fundamental *collection* types, `list`, `vector`, `set` and `map`. Again, these are all plain old Java objects, just like everything else so far. Clojure collections have some remarkable properties we'll learn later; but, for now the important difference between `list` and `vector` is that `list` behaves like a Java `LinkedList` while `vector` is more like a Java `ArrayList`. `Set` is a list of unique values. And `map` is a basic *key value pair* data structure. 109 | All of these types are sequential, meaning that they can all be cast into sequences. List is the only type that is naturally a sequence as it implements the Java `ISeq` interface. Sequences allows you to use all of the Clojure STL sequence functions (map, reduce, filter, etc) on these types. Something you might have noticed is that quotation mark in front of the `list`. You might also remember that the first thing inside parenthesis is always interpreted as a function to call, while still true the case for `list` is unique. The quotation mark is what is known as a *special form*. It yields the evaluation of the expression and returns a `list` of unevaluated values instead of interpreting it as a function and arguments. 110 | 111 | #### Keywords 112 | 113 | ```clojure 114 | (class (keyword "4")) ;; => clojure.lang.keyword 115 | (class :1) ;; => clojure.lang.keyword 116 | ``` 117 | 118 | Keywords are a special type in Clojure. They are used as identifiers, but they also work as functions. Keywords are always prepended with a colon. Keywords are commonly used as keys in collections. Keywords can also be used as functions and will evaluate to their own value inside a `map` or `set` when used as a function: `(:a {:a 4}) ;; => 4` 119 | 120 | #### Atoms 121 | 122 | ```clojure 123 | (class (atom 5)) ;; => clojure.lang.Atom 124 | ``` 125 | 126 | Atoms are how Clojure represents mutable data. Atoms are a special kind of reference type. Atoms can be created using the `atom` function. To read an atoms state, you will need to use the `deref` function. 127 | ```clojure 128 | (deref (atom 5)) ;; => 5 129 | @(atom 5) ;; => 5 130 | ``` 131 | The `@` is just a macro for deref, will get into macros later on. Clojure provides a handful of functions to change an atoms state, most commonly used are: `swap!` and `reset!`. 132 | 133 | ```clojure 134 | (def number-atom (atom 5)) 135 | (println @number-atom) ;; => 5 136 | (swap! number-atom inc) 137 | (println @number-atom) ;; => 6 138 | 139 | (def text-atom (atom "Hello")) 140 | (println @text-atom) ;; => "Hello" 141 | (reset! text-atom "World") 142 | (println @text-atom) ;; => "World" 143 | ``` 144 | 145 | Don't worry about `def` just yet, we'll get into that one later on. As you can see in the examples above, `reset!` works a little bit differently than `swap!`. `swap!` will take an atom as the first argument and a function that will apply a change on the atom. `swap!` also takes an optional third argument, this argument would be an argument that would be passed to the applying function: `(swap! a-list conj :a-value) ;; => '(:a-value)` *note* `swap!` returns nil, the arrow, in this example, indicates the new mutated version. `reset!` only takes two arguments, an atom and a new value. An interesting thing you might have noticed is the use of the exclamation mark. This is a convention in lisp languages. The exclamation mark indicates that their is a side-effect made by the function. When you see the exclamation mark, most likely, an atom has been changed. 146 | 147 | ## Special Forms 148 | 149 | Next we will briefly look at special forms. These are forms that are evaluated differently (*special*) than normal expressions. Special forms are not macros or functions, although they behave very similarly. Special forms are directly built in to the Clojure compiler. They are a fundamental building block of Clojure. 150 | Clojure has a dozen special forms, but generally you will only be using the following: 151 | 152 | #### def 153 | 154 | The `def` form creates a global *var* with a name and a value inside the current namespace. `def` only requires one argument, a name for the current var, the rest are optional. `def` can also be given a metadata map. This metadata map can contain a handful of useful meta information, such as: 155 | 156 | 1. `:private` 157 | a boolean indicating the access control for the var. If this key is not present, the default access is public (e.g. as if :private false). 158 | 159 | 2. `:doc` 160 | a string containing short (1-3 line) documentation for the var contents 161 | 162 | 3. `:test` 163 | a fn of no args that uses assert to check various operations. The var itself will be accessible during evaluation of a literal fn in the metadata map. 164 | 165 | 4. `:tag` 166 | a symbol naming a class or a Class object that indicates the Java type of the object in the var, or its return value if the object is a fn. 167 | 168 | ```clojure 169 | (ns user) ; Current namespace 170 | 171 | (def some-empty-var) ;; => #'user/some-empty-var 172 | (def some-var "Hello") ;; => #'user/some-var 173 | (def 174 | ^{:doc "Description of var" 175 | :private true} 176 | metadata-var 177 | "World") ;; => #'user/metadata-var 178 | 179 | ;; Vars don't need parenthesis. They always point to their respective value 180 | some-empty-var ;; => "Unbound: #'user/some-empty-var" 181 | some-var ;; => "Hello" 182 | metadata-var ;; => "World" 183 | ``` 184 | 185 | #### fn 186 | 187 | The `fn` form lets you define functions. Functions don't require a name, as they can be anonymous. `fn` form takes three arguments, an optional name, optional vector of arguments for the function body, and the function body itself. 188 | 189 | ```clojure 190 | (fn some-fn [] (println "Hello")) ;; => nothing 191 | (fn [] (println "World")) ;; => nothing 192 | ``` 193 | 194 | In the examples above, nothing gets printed out, the functions above never get called. To call a function, you need to add a pair of parenthesis. 195 | 196 | ```clojure 197 | ((fn some-fn [] (println "Hello"))) ;; => "Hello" 198 | ((fn [] (println "World"))) ;; => "World" 199 | ``` 200 | 201 | Functions can also be stored inside a `def` 202 | 203 | ```clojure 204 | (def some-fn (fn [] (println "Functions!"))) ;; => #'user/some-fn 205 | (some-fn) ;; => "Functions!" 206 | ``` 207 | 208 | There is also a shorthand for this. 209 | 210 | ```clojure 211 | (defn some-fn [value] (println value)) ;; => #'user/some-fn 212 | (some-fn "Functions!") ;; => "Functions!" 213 | ``` 214 | 215 | As we learned earlier, everything after the function position is interpreted as arguments for the function. 216 | 217 | #### let 218 | 219 | The `let` form lets you define local bindings between symbols and values. The bindings are always immutable and can *never be changed*. The bindings will only exist inside the body of the `let`. 220 | 221 | ```clojure 222 | (let [x 1 223 | y 2 224 | z y] 225 | (println x y z)) ;; => 1 2 2 226 | ``` 227 | 228 | A `let` can easily be used in conjunction with `defn` 229 | 230 | ```clojure 231 | (defn fn-with-locals [some-arg] 232 | (let [x 1 233 | y (+ x 1) 234 | z (+ y some-arg)] 235 | (println x y z))) ;; => #'user/fn-with-locals 236 | 237 | (fn-with-locals 5) ;; => 1 2 7 238 | ``` 239 | 240 | As mentioned above, these are the most common *special forms*. There are of course a lot more. There is a link to all special forms in the reference section of this page. Up next is examples. 241 | 242 | ## Examples 243 | 244 | Next we will be looking at very common functions, and we'll also be breaking them down to understand thoroughly how they work. 245 | 246 | ### Plus 247 | 248 | ```clojure 249 | (+ 1 2) ;; => 3 250 | (+ 1) ;; => 1 251 | (+) ;; => 0 252 | (+ 2 2 2 2 2) ;; => 10 253 | ``` 254 | 255 | The plus `+` function takes 0 to infinite amount of numbers as arguments and sums them. 256 | 257 | All arithmetic functions (- * / +) work the same way. They all take between 0 or 1 to infinite amount of arguments, and performs their respective arithmetic operation on the given arguments. 258 | 259 | ### Keyword 260 | 261 | ```clojure 262 | (:name {:name "Sam"}) ;; => "Sam" 263 | (:name {:id 5 :balance 500}) ;; => nil 264 | (:name {:id 10 :balance 300} "Simon") ;; => "Simon" 265 | (:name #{:name :age :balance}) ;; => :name 266 | ``` 267 | 268 | Keywords work as functions also. Keywords as functions work by finding themselves from a given collection. If the collection does not contain the given keyword, the function will return nil. Keywords can be given an extra *default* value argument. In case the keyword could not be found from the given collection the *default* value would be returned instead. 269 | 270 | ### Equality & Comparison & Predicate 271 | 272 | ```clojure 273 | (= 1 1) ;; => true 274 | (= "1" 1) ;; => false 275 | (= [1 2] [1 2]) ;; => true 276 | (= '("Milo" "Tonny" "Franke") '("Peter" "Glenn" "Joe")) ;; => false 277 | (= nil false) ;; => false 278 | (not (= 1 2)) ;; => true 279 | (not= 1 2) ;; => true 280 | (= 1 1 1 1 1) ;; => true 281 | (= 2 2 2 2 1) ;; => false 282 | (= {:user {:name "Peter"}} {:user {:name "Peter"}}) ;; => true 283 | ``` 284 | 285 | The equals function takes 1 to an infinite amount of arguments and compares them all. If all arguments are *exactly* the same, the function will return true, otherwise false. The equals function will always return a boolean. The not function will return the exact opposite of what equals will return. 286 | 287 | ```clojure 288 | (> 1 2) ;; => false 289 | (< 1 2) ;; => true 290 | (>= 10 10 9) ;; => true 291 | (<= 5 6 7) ;; => true 292 | ``` 293 | 294 | The comparison functions all work in the same way. Each of them take 1 to an infinite amount of arguments. Comparison functions always checks numbers in pairs. If a comparison function is given e.g the arguments `1 2 3` the first comparison would be between `1 2` and the second one between `2 3`. The will continue until there are no more arguments to compare. If all comparisons returned true then the function itself will return true, otherwise false. 295 | 296 | ```clojure 297 | (even? 2) ;; => true 298 | (odd? 2) ;; => false 299 | (number? "1") ;; => false 300 | (vector? '()) ;; => false 301 | (seq? {}) ;; => false 302 | (empty? []) ;; => true 303 | (some? nil) ;; => false 304 | (nil? nil) ;; => true 305 | ``` 306 | 307 | Clojure has a handful of predicate functions. Predicate functions are functions that will check if the given argument matches the predicate functions comparison clause. If the argument is a match, the functions will return true, otherwise false. One thing to keep in mind is that predicate functions always return a boolean as a result. Predicate functions can easily be identifiable by the question mark notation. Question marks at the end of functions should, in general, be considered predicate functions. 308 | 309 | 310 | ### Sequence & Collection functions 311 | 312 | ```clojure 313 | (map inc [1 2 3]) ;; => '(2 3 4) 314 | (filter odd? '(2 2 2 5 9 1 2 2) ;; => '(5 9 1) 315 | (reduce + [10 10 10 10 10]) ;; => 50 316 | (map :name [{:name "Sam" :age 22} {:name "Simon" :age 55}]) ;; => '("Sam" "Simon") 317 | (map + [12 34 5] '(2 3 4) #{1 2 11}) ;; => '(15 39 20) 318 | (map first {:name "Homer J, Simpson" :age 39 :title "Junior Vice President"}) ;; => '(:name :age :title) 319 | (assoc {} :name "Peggy Hill") ;; => {:name "Peggy Hill"} 320 | (dissoc {:name "Bill Dauterive" :occupation nil} :occupation) ;; => {:name "Bill Dauterive"} 321 | (count "Hank Hill") ;; => 9 322 | ``` 323 | 324 | Sequence functions are functions that take a *sequential type* (*map vector list string set*) and a function as arguments, and perform some sort of operation on the sequence and *return a new sequence*. E.g. `map` function will always return a sequence (list), even if you pass it a vector. 325 | 326 | Collection functions are functions that take any type that implements the `IPersistentCollection` interface as an argument and performs some sort of operation on the argument. `count` is unique in this case since it can also be given a string even though a string is not a collection. 327 | 328 | That's it for most common functions. More examples can be found from the [Clojure Cheatsheet](https://clojure.org/api/cheatsheet). 329 | 330 | ## Reader 331 | 332 | The reader is a unique feature of Clojure. You could call it an extra build step. This step is taken before the source code gets compiled. 333 | Clojure has two steps of compilation in a sense, first the reader will turn your Clojure code into Clojure data structures, and then the compiler will turn the data structures into Java byte code. You can actually test the reader with Clojure's `read-string` function. 334 | 335 | ```clojure 336 | (read-string "(+ 1 2 3)") ;; => (+ 1 2 3) 337 | ``` 338 | 339 | Why is this useful? Well, in Clojure the compilation and reading (parsing) of the code are completely separate. This is an essential part in Clojure. The reader allows us to change how the code behaves, or even what the code looks like. This is done with the help of *macros*. In the above example we see a straightforward relationship between the reader and the data structure it produces. However, as mentioned, we can manipulate the data structure with the help of *reader macros*. 340 | 341 | ```clojure 342 | (read-string "'(a b c)") ;; => (quote (a b c)) 343 | ``` 344 | Above we see an example of a *reader macro*. `'` is the *quote* macro. The *quote* macro yields unevaluated Clojure expression. 345 | Clojure offers a handful of *reader macros*. To get a full understanding on how the reader works and how to reader macros work, you can read everything about the reader here: https://clojure.org/reference/reader 346 | 347 | ## Macros 348 | 349 | Macros are one of the features that make Clojure amazing! Macros are quite special. Unlike normal functions, marcos are placed between the reader and compiler in the Clojure lifecycle, and unlike functions, macros are created with the `defmacro` special form. What this means, is that with macros we can manipulate unevaluated (yielded) data structures. We can essentially create new code and change existing code. 350 | 351 | Practical example: 352 | 353 | ```clojure 354 | (defmacro macro-fn [form] 355 | (drop-last form)) 356 | 357 | (macro-fn (+ 2 nil)) ;; => 2 358 | 359 | (defn normal-fn [form] 360 | (drop-last form)) 361 | 362 | (normal-fn (+ 2 nil)) ;; => java.lang.NullPointerException 363 | ``` 364 | 365 | Why does this happen? Clojure syntax is evaluated from the inside out, except in macros and special forms. Macros get passed unevaluated expressions, that's why we can manipulate the unevaluated expression however we wish. In the case of a function, the inner most expression will always get evaluated first, and the result of that expression will get passed to the function body. In the example above the inner most expression resulted in a `NullPointerException`. 366 | 367 | And that's it! We covered the bare basics of the language! Hopefully you understand how Clojure works and how to write basic expressions in Clojure. Hopefully you also have a grasp on the different *types* in Clojure, and how the language is structured. Next up we'll get to tackle some real coding challenges in the *koans* section. 368 | 369 | ## References 370 | 371 | 1. [Clojure for the Brave and TRUE](https://www.braveclojure.com/getting-started/) 372 | 2. [Clojure Special Forms](https://clojure.org/reference/special_forms) 373 | 3. [Clojure Syntax](https://clojure.org/guides/learn/syntax) 374 | 4. [Clojure Types](https://aphyr.com/posts/302-clojure-from-the-ground-up-basic-types) 375 | -------------------------------------------------------------------------------- /materials/section-0/structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futurice/clojure-workshop/c1e64a03e1115289898838d4f19f727bc51e2b34/materials/section-0/structure.png -------------------------------------------------------------------------------- /materials/section-1/.gitignore: -------------------------------------------------------------------------------- 1 | /pom.xml 2 | *jar 3 | /lib 4 | /classes 5 | /native 6 | /target 7 | /.lein-failures 8 | /checkouts 9 | /.lein-deps-sum 10 | .nrepl-port 11 | .DS_Store 12 | *.#* 13 | *#* 14 | *.classpath 15 | *.project 16 | *.settings 17 | *.pyc 18 | *.dot 19 | .cake/ 20 | .lein-failures 21 | .lein-deps-sum 22 | docs/** 23 | autodoc/** 24 | -------------------------------------------------------------------------------- /materials/section-1/README.md: -------------------------------------------------------------------------------- 1 | # Programming & REPL 2 | 3 | In this section we'll finally get our hands dirty with some coding. 4 | We'll be going through the core functions and at the same time we'll do some challenges, koans. The koans are located in the `src` folder. 5 | 6 | This section is a good part to familiarize yourself with the Clojure REPL (Read - Evaluate - Print - Loop). 7 | To start the REPL, run the following command in the terminal: `lein repl`. 8 | Nightcode has an inbuilt REPL, just press the *run with REPL* button and you are all set. 9 | 10 | A good way to try and solve the following koans would be to first try to find a viable solution with the help of the REPL and then in the source file. 11 | 12 | Some of the koans can be somewhat challenging. https://clojuredocs.org/ and the [Clojure Cheat Sheet](https://clojure.org/api/cheatsheet) are good sources to find the most relevant and helpful functions to solve the koans. 13 | 14 | If you're wondering how Clojure's STL functions work, the `doc` and `source` functions are a good way to peek at them 15 | 16 | ```clojure 17 | (doc +) 18 | ------------------------- 19 | clojure.core/+ 20 | ([] [x] [x y] [x y & more]) 21 | Returns the sum of nums. (+) returns 0. Does not auto-promote 22 | longs, will throw on overflow. See also: +' 23 | 24 | (source +) 25 | ------------------------- 26 | (defn + 27 | "Returns the sum of nums. (+) returns 0. Does not auto-promote 28 | longs, will throw on overflow. See also: +'" 29 | {:inline (nary-inline 'add 'unchecked_add) 30 | :inline-arities >1? 31 | :added "1.2"} 32 | ([] 0) 33 | ([x] (cast Number x)) 34 | ([x y] (. clojure.lang.Numbers (add x y))) 35 | ([x y & more] 36 | (reduce1 + (+ x y) more))) 37 | ``` 38 | 39 | ### Nightcode 40 | 41 | Nightcode is a lightweight Clojure editor designed primarily for teaching. Nightcode is optional for this workshop. But it already comes equipped with [parinfer](https://github.com/shaunlebron/parinfer) which is a handy plugin that makes writing Clojure a cinch. 42 | 43 | Example of parinfer: 44 | 45 | ![Example](https://camo.githubusercontent.com/142ec87e9c4ab1863c3dc4c26807f6bca4ef3f5b/687474703a2f2f7a697070792e6766796361742e636f6d2f57656972644f6464426c756566696e74756e612e676966) 46 | 47 | Parinfer will automatically add parenthesis and brackets for you. All you have to do is worry about indentation. 48 | 49 | ### Running the Koans 50 | 51 | To run the koans, simply run: 52 | 53 | `lein koan run` with leiningen 54 | 55 | `script/run` on Mac/\*nix (Optional) 56 | 57 | `script\run` on Windows (Optional) 58 | 59 | As you save your files with the correct answers, section-1 will automatically advance you to the next koan or file. 60 | 61 | When you execute `run` you'll see something like this: 62 | 63 | Problem in /home/paduan/code/section-1/src/koans/equalities.clj 64 | --------------------- 65 | Assertion failed! 66 | We shall contemplate truth by testing reality, via equality. 67 | (= __ true) 68 | 69 | The output is telling you that you have a failing test in `src/koans/equalities.clj`. Open that file up and make it pass! In general, you just fill in the blanks to make tests pass. Sometimes there are several (or even an infinite number) of correct answers: any of them will work in these cases. 70 | 71 | The koans differ from normal TDD in that the tests are already written for you, so you'll have to pay close attention to the failure messages, because up until the very end, making a test pass just means that the next failure message comes up. 72 | 73 | You might notice that some of the koans are commented out - this means that you're welcome to skip these in order to keep to our time schedule. But, if you have some time to spare before we start the next section, I'd highly recommend going back and trying to tackle some. 74 | -------------------------------------------------------------------------------- /materials/section-1/project.clj: -------------------------------------------------------------------------------- 1 | (defproject section-1 "0.1.0-SNAPSHOT" 2 | :description "FutuFamily Clojure Workshop - Section 1" 3 | :dependencies [[org.clojure/clojure "1.10.0"] 4 | [koan-engine "0.2.5"]] 5 | :dev-dependencies [[lein-koan "0.1.5"]] 6 | :profiles {:dev {:dependencies [[lein-koan "0.1.5"]]}} 7 | :repl-options {:init-ns koan-engine.runner 8 | :init ^:displace (do (use '[koan-engine.core]))} 9 | :plugins [[lein-koan "0.1.5"]] 10 | :main koan-engine.runner/exec) 11 | -------------------------------------------------------------------------------- /materials/section-1/resources/koans.clj: -------------------------------------------------------------------------------- 1 | [["01_equalities"] 2 | 3 | ["02_strings"] 4 | 5 | ["03_lists"] 6 | 7 | ["04_vectors"] 8 | 9 | ["05_sets"] 10 | 11 | ["06_maps"] 12 | 13 | ["07_functions"] 14 | 15 | ["08_conditionals"] 16 | 17 | ["09_higher_order_functions"] 18 | 19 | ["10_atoms"] 20 | 21 | ["11_sequence_comprehensions"] 22 | 23 | ["12_lazy_sequences"] 24 | 25 | ["13_threading"] 26 | 27 | ["14_recursion"] 28 | 29 | ["15_creating_functions"] 30 | 31 | ["16_destructuring"] 32 | 33 | ["17_runtime_polymorphism"] 34 | 35 | ["18_refs"]] 36 | -------------------------------------------------------------------------------- /materials/section-1/script/bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | lein deps 4 | mkdir -p releases 5 | zip -r releases/section-1-`date +"%Y-%m-%d_%H-%M"`.zip \ 6 | . \ 7 | -x "./.git/*" \ 8 | -x "releases/*" 9 | 10 | echo 11 | echo "Don't forget to upload the zipfile" 12 | echo " to https://github.com///downloads" 13 | echo `ls -t releases/section-1-*.zip | head -n1` 14 | echo 15 | -------------------------------------------------------------------------------- /materials/section-1/script/repl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | CLASSPATH=src 3 | 4 | for f in lib/*.jar lib/dev/*.jar resources/; do 5 | CLASSPATH=$CLASSPATH:$f 6 | done 7 | 8 | if [ "$OSTYPE" = "cygwin" ]; then 9 | CLASSPATH=`cygpath -wp $CLASSPATH` 10 | fi 11 | 12 | java -Xmx1G -cp $CLASSPATH jline.ConsoleRunner clojure.main 13 | -------------------------------------------------------------------------------- /materials/section-1/script/repl.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setLocal EnableDelayedExpansion 3 | 4 | set CLASSPATH=" 5 | for %%j in (".\lib\*.jar") do ( 6 | set CLASSPATH=!CLASSPATH!;%%~fj 7 | ) 8 | set CLASSPATH=!CLASSPATH!" 9 | set CLASSPATH=%CLASSPATH%;src;resources 10 | 11 | set JLINE=jline.ConsoleRunner 12 | 13 | java -Xmx1G -cp %CLASSPATH% %JLINE% clojure.main 14 | -------------------------------------------------------------------------------- /materials/section-1/script/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | CLASSPATH=src 3 | 4 | for f in lib/*.jar lib/dev/*.jar resources/; do 5 | CLASSPATH=$CLASSPATH:$f 6 | done 7 | 8 | if [ "$OSTYPE" = "cygwin" ]; then 9 | CLASSPATH=`cygpath -wp $CLASSPATH` 10 | fi 11 | 12 | java -cp "$CLASSPATH" clojure.main script/run.clj 13 | echo 14 | -------------------------------------------------------------------------------- /materials/section-1/script/run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setLocal EnableDelayedExpansion 3 | 4 | set CLASSPATH=" 5 | for %%j in (".\lib\*.jar", ".\lib\dev\*.jar") do ( 6 | set CLASSPATH=!CLASSPATH!;%%~fj 7 | ) 8 | set CLASSPATH=!CLASSPATH!" 9 | set CLASSPATH=%CLASSPATH%;src;resources 10 | 11 | java -Xmx1G -cp %CLASSPATH% clojure.main script\run.clj 12 | -------------------------------------------------------------------------------- /materials/section-1/script/run.clj: -------------------------------------------------------------------------------- 1 | (require 'koan-engine.runner) 2 | (koan-engine.runner/exec "run") 3 | -------------------------------------------------------------------------------- /materials/section-1/script/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | CLASSPATH=src 3 | 4 | 5 | for f in lib/*.jar lib/dev/*.jar resources/; do 6 | CLASSPATH=$CLASSPATH:$f 7 | done 8 | 9 | if [ "$OSTYPE" = "cygwin" ]; then 10 | CLASSPATH=`cygpath -wp $CLASSPATH` 11 | fi 12 | 13 | java -cp "$CLASSPATH" clojure.main script/test.clj 14 | echo 15 | -------------------------------------------------------------------------------- /materials/section-1/script/test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setLocal EnableDelayedExpansion 3 | 4 | set CLASSPATH=" 5 | for %%j in (".\lib\*.jar", ".\lib\dev\*.jar") do ( 6 | set CLASSPATH=!CLASSPATH!;%%~fj 7 | ) 8 | set CLASSPATH=!CLASSPATH!" 9 | set CLASSPATH=%CLASSPATH%;src;resources 10 | 11 | java -Xmx1G -cp %CLASSPATH% clojure.main script\test.clj 12 | -------------------------------------------------------------------------------- /materials/section-1/script/test.clj: -------------------------------------------------------------------------------- 1 | (require 'koan-engine.runner) 2 | (koan-engine.runner/exec "test") 3 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/01_equalities.clj: -------------------------------------------------------------------------------- 1 | (ns koans.01-equalities 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (meditations 5 | "We shall contemplate truth by testing reality, via equality" 6 | (= __ true) 7 | 8 | "To understand reality, we must compare our expectations against reality" 9 | (= __ (+ 1 1)) 10 | 11 | ;; Optional 12 | "You can test equality of many things" 13 | (= (+ 3 4) 7 (+ 2 __)) 14 | 15 | "Some things may appear different, but be the same" 16 | (= __ (= 2 2/1)) 17 | 18 | "You cannot generally float to heavens of integers" 19 | (= __ (= 2 2.0)) 20 | 21 | "But a looser equality is also possible" 22 | (= __ (== 2.0 2)) 23 | 24 | "Something is not equal to nothing" 25 | (= __ (not (= 1 nil))) 26 | 27 | "Strings, and keywords, and symbols: oh my!" 28 | (= __ (= "hello" :hello 'hello)) 29 | 30 | "Make a keyword with your keyboard" 31 | (= :hello (keyword __)) 32 | 33 | "Symbolism is all around us" 34 | (= 'hello (symbol __)) 35 | 36 | "What could be equivalent to nothing?" 37 | (= __ nil) 38 | 39 | "When things cannot be equal, they must be different" 40 | (not= :fill-in-the-blank __)) 41 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/02_strings.clj: -------------------------------------------------------------------------------- 1 | (ns koans.02-strings 2 | (:require [koan-engine.core :refer :all] 3 | [clojure.string :as string])) 4 | 5 | (meditations 6 | "A string is nothing more than text surrounded by double quotes" 7 | (= __ "hello") 8 | 9 | "But double quotes are just magic on top of something deeper" 10 | (= __ (str 'world)) 11 | 12 | "You can do more than create strings, you can put them together" 13 | (= "Cool right?" (str __ __)) 14 | 15 | "You can even get certain characters" 16 | (= \C (get "Characters" __)) 17 | 18 | "Or even count the characters" 19 | (= __ (count "Hello World")) 20 | 21 | "But strings and characters are not the same" 22 | (= __ (= \c "c")) 23 | 24 | "What if you only wanted to get part of a string?" 25 | (= "World" (subs "Hello World" __ __)) 26 | 27 | "How about joining together elements in a list?" 28 | (= __ (string/join '(1 2 3))) 29 | 30 | "What if you wanted to separate them out?" 31 | (= "1, 2, 3" (string/join __ '(1 2 3))) 32 | 33 | "Maybe you want to separate out all your lines" 34 | (= [__ __ __] (string/split-lines "1\n2\n3")) 35 | 36 | ;; Optional 37 | "You may want to make sure your words are backwards" 38 | (= __ (string/reverse "hello")) 39 | 40 | "Maybe you want to find the index of the first occurrence of a substring" 41 | (= 0 (string/index-of "hello world" __)) 42 | 43 | "Or maybe the last index of the same" 44 | (= __ (string/last-index-of "hello world, hello" "hello"))) 45 | 46 | "But when something doesn't exist, nothing is found" 47 | (= __ (string/index-of "hello world" "bob")) 48 | 49 | "Sometimes you don't want whitespace cluttering the front and back" 50 | (= __ (string/trim " \nhello world \t \n")) 51 | 52 | "You can check if something is a char" 53 | (= __ (char? \c)) 54 | 55 | "But it may not be" 56 | (= __ (char? "a")) 57 | 58 | "But chars aren't strings" 59 | (= __ (string? \b)) 60 | 61 | "Strings are strings" 62 | (= true (string? __)) 63 | 64 | "Some strings may be blank" 65 | (= __ (string/blank? "")) 66 | 67 | "Even if at first glance they aren't" 68 | (= __ (string/blank? " \n \t ")) 69 | 70 | "However, most strings aren't blank" 71 | (= __ (string/blank? "hello?\nare you out there?")) 72 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/03_lists.clj: -------------------------------------------------------------------------------- 1 | (ns koans.03-lists 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (meditations 5 | "Lists can be expressed by function or a quoted form" 6 | (= '(__ __ __ __ __) (list 1 2 3 4 5)) 7 | 8 | "They are Clojure seqs (sequences), so they allow access to the first" 9 | (= __ (first '(1 2 3 4 5))) 10 | 11 | "As well as the rest" 12 | (= __ (rest '(1 2 3 4 5))) 13 | 14 | "Count your blessings" 15 | (= __ (count '(dracula dooku chocula))) 16 | 17 | "Before they are gone" 18 | (= __ (count '())) 19 | 20 | "The rest, when nothing is left, is empty" 21 | (= __ (rest '(100))) 22 | 23 | "Construction by adding an element to the front is easy" 24 | (= __ (cons :a '(:b :c :d :e))) 25 | 26 | "Conjoining an element to a list isn't hard either" 27 | (= __ (conj '(:a :b :c :d) :e))) 28 | 29 | "You can use a list like a stack to get the first element" 30 | (= __ (peek '(:a :b :c :d :e))) 31 | 32 | "Or the others" 33 | (= __ (pop '(:a :b :c :d :e))) 34 | 35 | "But watch out if you try to pop nothing" 36 | (= __ (try 37 | (pop '()) 38 | (catch IllegalStateException e 39 | "No dice!")) 40 | 41 | "The rest of nothing isn't so strict" 42 | (= __ (try 43 | (rest '()) 44 | (catch IllegalStateException e 45 | "No dice!")))) 46 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/04_vectors.clj: -------------------------------------------------------------------------------- 1 | (ns koans.04-vectors 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (meditations 5 | "You can use vectors in clojure as array-like structures" 6 | (= __ (count [42])) 7 | 8 | "You can create a vector from a list" 9 | (= __ (vec '(1))) 10 | 11 | "Or from some elements" 12 | (= __ (vector nil nil)) 13 | 14 | "But you can populate it with any number of elements at once" 15 | (= [1 __] (vec '(1 2))) 16 | 17 | "Conjoining to a vector is different than to a list" 18 | (= __ (conj [111 222] 333)) 19 | 20 | "You can get the first element of a vector like so" 21 | (= __ (first [:peanut :butter :and :jelly])) 22 | 23 | "And the last in a similar fashion" 24 | (= __ (last [:peanut :butter :and :jelly])) 25 | 26 | "Or any index if you wish" 27 | (= __ (nth [:peanut :butter :and :jelly] 3)) 28 | 29 | "You can also slice a vector" 30 | (= __ (subvec [:peanut :butter :and :jelly] 1 3)) 31 | 32 | "Equality with collections is in terms of values" 33 | (= (list 1 2 3) (vector 1 2 __))) 34 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/05_sets.clj: -------------------------------------------------------------------------------- 1 | (ns koans.05-sets 2 | (:require [koan-engine.core :refer :all] 3 | [clojure.set :as set])) 4 | 5 | (meditations 6 | "You can create a set by converting another collection" 7 | (= #{3} (set __)) 8 | 9 | "Counting them is like counting other collections" 10 | (= __ (count #{1 2 3})) 11 | 12 | "Remember that a set is a *mathematical* set" 13 | (= __ (set '(1 1 2 2 3 3 4 4 5 5))) 14 | 15 | "You can ask clojure for the union of two sets" 16 | (= __ (set/union #{1 2 3 4} #{2 3 5})) 17 | 18 | "And also the intersection" 19 | (= __ (set/intersection #{1 2 3 4} #{2 3 5})) 20 | 21 | "But don't forget about the difference" 22 | (= __ (set/difference #{1 2 3 4 5} #{2 3 5}))) 23 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/06_maps.clj: -------------------------------------------------------------------------------- 1 | (ns koans.06-maps 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (meditations 5 | "Don't get lost when creating a map" 6 | (= {:a 1 :b 2} (hash-map :a 1 __ __)) 7 | 8 | "A value must be supplied for each key" 9 | (= {:a 1} (hash-map :a __)) 10 | 11 | "The size is the number of entries" 12 | (= __ (count {:a 1 :b 2})) 13 | 14 | "You can look up the value for a given key" 15 | (= __ (get {:a 1 :b 2} :b)) 16 | 17 | "Maps can be used as functions to do lookups" 18 | (= __ ({:a 1 :b 2} :a)) 19 | 20 | "And so can keywords" 21 | (= __ (:a {:a 1 :b 2})) 22 | 23 | "But map keys need not be keywords" 24 | (= __ ({2010 "Vancouver" 2014 "Sochi" 2018 "PyeongChang"} 2014)) 25 | 26 | "You may not be able to find an entry for a key" 27 | (= __ (get {:a 1 :b 2} :c)) 28 | 29 | "But you can provide your own default" 30 | (= __ (get {:a 1 :b 2} :c :key-not-found)) 31 | 32 | "You can find out if a key is present" 33 | (= __ (contains? {:a nil :b nil} :b)) 34 | 35 | "Or if it is missing" 36 | (= __ (contains? {:a nil :b nil} :c)) 37 | 38 | "Maps are immutable, but you can create a new and improved version" 39 | (= {1 "January" 2 __} (assoc {1 "January"} 2 "February")) 40 | 41 | "You can also create a new version with an entry removed" 42 | (= {__ __} (dissoc {1 "January" 2 "February"} 2)) 43 | 44 | "Create a new map by merging" 45 | (= {:a 1 :b 2 __ __} (merge {:a 1 :b 2} {:c 3})) 46 | 47 | "Specify how to handle entries with same keys when merging" 48 | (= {:a 1 :b __ :c 3} (merge-with + {:a 1 :b 1} {:b 1 :c 3})) 49 | 50 | "Often you will need to get the keys, but the order is undependable" 51 | (= (list __ __ __) 52 | (sort (keys { 2014 "Sochi" 2018 "PyeongChang" 2010 "Vancouver"}))) 53 | 54 | "You can get the values in a similar way" 55 | (= (list __ __ __) 56 | (sort (vals {2010 "Vancouver" 2014 "Sochi" 2018 "PyeongChang"}))) 57 | 58 | "You can even iterate over the map entries as a seq" 59 | (= {:a __ :b __} 60 | (into {} 61 | (map 62 | (fn [[k v]] [k (inc v)]) 63 | {:a 1 :b 2})))) 64 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/07_functions.clj: -------------------------------------------------------------------------------- 1 | (ns koans.07-functions 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (defn multiply-by-ten [n] 5 | (* 10 n)) 6 | 7 | (defn square [n] (* n n)) 8 | 9 | (meditations 10 | "Calling a function is like giving it a hug with parentheses" 11 | (= __ (square 9)) 12 | 13 | "Functions are usually defined before they are used" 14 | (= __ (multiply-by-ten 2)) 15 | 16 | "But they can also be defined inline" 17 | (= __ ((fn [n] (* 5 n)) 2)) 18 | 19 | "Or using an even shorter syntax" 20 | (= __ (#(* 15 %) 4)) 21 | 22 | "Even anonymous functions may take multiple arguments" 23 | (= __ (#(+ %1 %2 %3) 4 5 6)) 24 | 25 | "Arguments can also be skipped" 26 | (= __ (#(str "AA" %2) "bb" "CC")) 27 | 28 | "One function can beget another" 29 | (= 9 (((fn [] ___)) 4 5)) 30 | 31 | "Functions can also take other functions as input" 32 | (= 20 ((fn [f] (f 4 5)) 33 | ___)) 34 | 35 | "Higher-order functions take function arguments" 36 | (= 25 (___ 37 | (fn [n] (* n n)))) 38 | 39 | "But they are often better written using the names of functions" 40 | (= 25 (___ square))) 41 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/08_conditionals.clj: -------------------------------------------------------------------------------- 1 | (ns koans.08-conditionals 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (defn explain-exercise-velocity [exercise-term] 5 | (case exercise-term 6 | :bicycling "pretty fast" 7 | :jogging "not super fast" 8 | :walking "not fast at all" 9 | "is that even exercise?")) 10 | 11 | (meditations 12 | "You will face many decisions" 13 | (= __ (if (false? (= 4 5)) 14 | :a 15 | :b)) 16 | 17 | "Some of them leave you no alternative" 18 | (= __ (if (> 4 3) 19 | [])) 20 | 21 | "And in such a situation you may have nothing" 22 | (= __ (if (nil? 0) 23 | [:a :b :c])) 24 | 25 | "In others your alternative may be interesting" 26 | (= :glory (if (not (empty? ())) 27 | :doom 28 | __)) 29 | 30 | "You may have a multitude of possible paths" 31 | (let [x 5] 32 | (= :your-road (cond (= x __) :road-not-taken 33 | (= x __) :another-road-not-taken 34 | :else __))) 35 | 36 | "Or your fate may be sealed" 37 | (= 'doom (if-not (zero? __) 38 | 'doom 39 | 'more-doom)) 40 | 41 | "In case of emergency, go fast" 42 | (= "pretty fast" 43 | (explain-exercise-velocity __)) 44 | 45 | "But admit it when you don't know what to do" 46 | (= __ 47 | (explain-exercise-velocity :watching-tv)) 48 | ) 49 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/09_higher_order_functions.clj: -------------------------------------------------------------------------------- 1 | (ns koans.09-higher-order-functions 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (meditations 5 | "The map function relates a sequence to another" 6 | (= [__ __ __] (map (fn [x] (* 4 x)) [1 2 3])) 7 | 8 | "You may create that mapping" 9 | (= [1 4 9 16 25] (map (fn [x] __) [1 2 3 4 5])) 10 | 11 | "Or use the names of existing functions" 12 | (= __ (map nil? [:a :b nil :c :d])) 13 | 14 | "A filter can be strong" 15 | (= __ (filter (fn [x] false) '(:anything :goes :here))) 16 | 17 | "Or very weak" 18 | (= __ (filter (fn [x] true) '(:anything :goes :here))) 19 | 20 | "Or somewhere in between" 21 | (= [10 20 30] (filter (fn [x] __) [10 20 30 40 50 60 70 80])) 22 | 23 | "Maps and filters may be combined" 24 | (= [10 20 30] (map (fn [x] __) (filter (fn [x] __) [1 2 3 4 5 6 7 8]))) 25 | 26 | "Reducing can increase the result" 27 | (= __ (reduce (fn [a b] (* a b)) [1 2 3 4])) 28 | 29 | "You can start somewhere else" 30 | (= 2400 (reduce (fn [a b] (* a b)) __ [1 2 3 4])) 31 | 32 | "Numbers are not the only things one can reduce" 33 | (= "longest" (reduce (fn [a b] 34 | (if (< __ __) b a)) 35 | ["which" "word" "is" "longest"]))) 36 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/10_atoms.clj: -------------------------------------------------------------------------------- 1 | (ns koans.10-atoms 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (def atomic-clock (atom 0)) 5 | 6 | (meditations 7 | "Atoms are like refs" 8 | (= __ @atomic-clock) 9 | 10 | "You can change at the swap meet" 11 | (= __ (do 12 | (swap! atomic-clock inc) 13 | @atomic-clock)) 14 | 15 | "Keep taxes out of this: swapping requires no transaction" 16 | (= 5 (do 17 | __ 18 | @atomic-clock)) 19 | 20 | "Any number of arguments might happen during a swap" 21 | (= __ (do 22 | (swap! atomic-clock + 1 2 3 4 5) 23 | @atomic-clock)) 24 | 25 | "Atomic atoms are atomic" 26 | (= __ (do 27 | (compare-and-set! atomic-clock 100 :fin) 28 | @atomic-clock)) 29 | 30 | "When your expectations are aligned with reality, things proceed that way" 31 | (= :fin (do 32 | (compare-and-set! __ __ __) 33 | @atomic-clock))) 34 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/11_sequence_comprehensions.clj: -------------------------------------------------------------------------------- 1 | (ns koans.11-sequence-comprehensions 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (meditations 5 | "Sequence comprehensions can bind each element in turn to a symbol" 6 | (= __ 7 | (for [x (range 6)] 8 | x)) 9 | 10 | "They can easily emulate mapping" 11 | (= '(0 1 4 9 16 25) 12 | (map (fn [x] (* x x)) 13 | (range 6)) 14 | (for [x (range 6)] 15 | __)) 16 | 17 | "And also filtering" 18 | (= '(1 3 5 7 9) 19 | (filter odd? (range 10)) 20 | (for [x __ :when (odd? x)] 21 | x)) 22 | 23 | "Combinations of these transformations is trivial" 24 | (= '(1 9 25 49 81) 25 | (map (fn [x] (* x x)) 26 | (filter odd? (range 10))) 27 | (for [x (range 10) :when __] 28 | __)) 29 | 30 | "More complex transformations simply take multiple binding forms" 31 | (= [[:top :left] [:top :middle] [:top :right] 32 | [:middle :left] [:middle :middle] [:middle :right] 33 | [:bottom :left] [:bottom :middle] [:bottom :right]] 34 | (for [row [:top :middle :bottom] 35 | column [:left :middle :right]] 36 | __))) 37 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/12_lazy_sequences.clj: -------------------------------------------------------------------------------- 1 | (ns koans.12-lazy-sequences 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (meditations 5 | "There are many ways to generate a sequence" 6 | (= __ (range 1 5)) 7 | 8 | "The range starts at the beginning by default" 9 | (= __ (range 5)) 10 | 11 | "Only take what you need when the sequence is large" 12 | (= [0 1 2 3 4 5 6 7 8 9] 13 | (take __ (range 100))) 14 | 15 | "Or limit results by dropping what you don't need" 16 | (= [95 96 97 98 99] 17 | (drop __ (range 100))) 18 | 19 | "Iteration provides an infinite lazy sequence" 20 | (= __ (take 8 (iterate (fn [x] (* x 2)) 1))) 21 | 22 | "Repetition is key" 23 | (= [:a :a :a :a :a :a :a :a :a :a] 24 | (repeat 10 __)) 25 | 26 | "Iteration can be used for repetition" 27 | (= (repeat 100 "hello") 28 | (take 100 (iterate ___ "hello")))) 29 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/13_threading.clj: -------------------------------------------------------------------------------- 1 | (ns koans.13-threading 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (meditations 5 | "Calling many functions on a single piece of data can be hard to digest" 6 | (= __ (first (.split (.replace (.toUpperCase "f u t u") "F" "T") " "))) 7 | 8 | 9 | "But we can make them more consumable by threading the needle" 10 | (= __ (-> "a i t o" 11 | (.replace "o" "i") 12 | (.replace "a" "ä") 13 | .toUpperCase)) 14 | 15 | "But sometimes we want to thread the needle in a different order" 16 | (= __ (->> (iterate inc 0) 17 | (map #(mod % 10)) 18 | (take 10))) 19 | ) 20 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/14_recursion.clj: -------------------------------------------------------------------------------- 1 | (ns koans.14-recursion 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (defn is-even? [n] 5 | (if (= n 0) 6 | __ 7 | (___ (is-even? (dec n))))) 8 | 9 | (defn is-even-bigint? [n] 10 | (loop [n n 11 | acc true] 12 | (if (= n 0) 13 | __ 14 | (recur (dec n) (not acc))))) 15 | 16 | (defn recursive-reverse [coll] 17 | __) 18 | 19 | (defn factorial [n] 20 | __) 21 | 22 | (meditations 23 | "Recursion ends with a base case" 24 | (= true (is-even? 0)) 25 | 26 | "And starts by moving toward that base case" 27 | (= false (is-even? 1)) 28 | 29 | "Having too many stack frames requires explicit tail calls with recur" 30 | (= false (is-even-bigint? 100003N)) 31 | 32 | "Reversing directions is easy when you have not gone far" 33 | (= '(1) (recursive-reverse [1])) 34 | 35 | "Yet it becomes more difficult the more steps you take" 36 | (= '(6 5 4 3 2) (recursive-reverse [2 3 4 5 6])) 37 | 38 | "Simple things may appear simple." 39 | (= 1 (factorial 1)) 40 | 41 | "They may require other simple steps." 42 | (= 2 (factorial 2)) 43 | 44 | "Sometimes a slightly bigger step is necessary" 45 | (= 6 (factorial 3)) 46 | 47 | "And eventually you must think harder" 48 | (= 24 (factorial 4)) 49 | 50 | "You can even deal with very large numbers" 51 | (< 1000000000000000000000000N (factorial 1000N)) 52 | 53 | "But what happens when the machine limits you?" 54 | (< 1000000000000000000000000N (factorial 100003N))) 55 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/15_creating_functions.clj: -------------------------------------------------------------------------------- 1 | (ns koans.15-creating-functions 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (defn square [x] (* x x)) 5 | 6 | (meditations 7 | "One may know what they seek by knowing what they do not seek" 8 | (= [__ __ __] (let [not-a-symbol? (complement symbol?)] 9 | (map not-a-symbol? [:a 'b "c"]))) 10 | 11 | "Praise and 'complement' may help you separate the wheat from the chaff" 12 | (= [:wheat "wheat" 'wheat] 13 | (let [not-nil? ___] 14 | (filter not-nil? [nil :wheat nil "wheat" nil 'wheat nil]))) 15 | 16 | "Partial functions allow procrastination" 17 | (= 20 (let [multiply-by-5 (partial * 5)] 18 | (___ __))) 19 | 20 | "Don't forget: first things first" 21 | (= [__ __ __ __] 22 | (let [ab-adder (partial concat [:a :b])] 23 | (ab-adder [__ __]))) 24 | 25 | "Functions can join forces as one 'composed' function" 26 | (= 25 (let [inc-and-square (comp square inc)] 27 | (inc-and-square __))) 28 | 29 | "Have a go on a double dec-er" 30 | (= __ (let [double-dec (comp dec dec)] 31 | (double-dec 10))) 32 | 33 | "Be careful about the order in which you mix your functions" 34 | (= 99 (let [square-and-dec ___] 35 | (square-and-dec 10)))) 36 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/16_destructuring.clj: -------------------------------------------------------------------------------- 1 | (ns koans.16-destructuring 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (def test-address 5 | {:street-address "123 Test Lane" 6 | :city "Testerville" 7 | :state "TX"}) 8 | 9 | (meditations 10 | "Destructuring is an arbiter: it breaks up arguments" 11 | (= __ ((fn [[a b]] (str b a)) 12 | [:foo :bar])) 13 | 14 | "Whether in function definitions" 15 | (= (str "An Oxford comma list of apples, " 16 | "oranges, " 17 | "and pears.") 18 | ((fn [[a b c]] __) 19 | ["apples" "oranges" "pears"])) 20 | 21 | "Or in let expressions" 22 | (= "Rich Hickey aka The Clojurer aka Go Time aka Lambda Guru" 23 | (let [[first-name last-name & aliases] 24 | (list "Rich" "Hickey" "The Clojurer" "Go Time" "Lambda Guru")] 25 | __)) 26 | 27 | "You can regain the full argument if you like arguing" 28 | (= {:original-parts ["Stephen" "Hawking"] :named-parts {:first "Stephen" :last "Hawking"}} 29 | (let [[first-name last-name :as full-name] ["Stephen" "Hawking"]] 30 | __)) 31 | 32 | "Break up maps by key" 33 | (= "123 Test Lane, Testerville, TX" 34 | (let [{street-address :street-address, city :city, state :state} test-address] 35 | __)) 36 | 37 | "Or more succinctly" 38 | (= "123 Test Lane, Testerville, TX" 39 | (let [{:keys [street-address __ __]} test-address] 40 | __)) 41 | 42 | "All together now!" 43 | (= "Test Testerson, 123 Test Lane, Testerville, TX" 44 | (___ ["Test" "Testerson"] test-address))) 45 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/17_runtime_polymorphism.clj: -------------------------------------------------------------------------------- 1 | (ns koans.17-runtime-polymorphism 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (defn hello 5 | ([] "Hello World!") 6 | ([a] (str "Hello, you silly " a ".")) 7 | ([a & more] (str "Hello to this group: " 8 | (apply str 9 | (interpose ", " (cons a more))) 10 | "!"))) 11 | 12 | (defmulti diet (fn [x] (:eater x))) 13 | (defmethod diet :herbivore [a] __) 14 | (defmethod diet :carnivore [a] __) 15 | (defmethod diet :default [a] __) 16 | 17 | 18 | (meditations 19 | ;; "Some functions can be used in different ways - with no arguments" 20 | ;; (= __ (hello)) 21 | 22 | ;; "With one argument" 23 | ;; (= __ (hello "world")) 24 | 25 | ;; "Or with many arguments" 26 | ;; (= __ 27 | ;; (hello "Peter" "Paul" "Mary")) 28 | 29 | ;; "Multimethods allow more complex dispatching" 30 | ;; (= "Bambi eats veggies." 31 | ;; (diet {:species "deer" :name "Bambi" :age 1 :eater :herbivore})) 32 | 33 | ;; "Animals have different names" 34 | ;; (= "Thumper eats veggies." 35 | ;; (diet {:species "rabbit" :name "Thumper" :age 1 :eater :herbivore})) 36 | 37 | ;; "Different methods are used depending on the dispatch function result" 38 | ;; (= "Simba eats animals." 39 | ;; (diet {:species "lion" :name "Simba" :age 1 :eater :carnivore})) 40 | 41 | ;; "You may use a default method when no others match" 42 | ;; (= "I don't know what Rich Hickey eats." 43 | ;; (diet {:name "Rich Hickey"})) 44 | ) 45 | -------------------------------------------------------------------------------- /materials/section-1/src/koans/18_refs.clj: -------------------------------------------------------------------------------- 1 | (ns koans.18-refs 2 | (:require [koan-engine.core :refer :all])) 3 | 4 | (def the-world (ref "hello")) 5 | (def bizarro-world (ref {})) 6 | 7 | (meditations 8 | ;; "In the beginning, there was a word" 9 | ;; (= __ (deref the-world)) 10 | 11 | ;; "You can get the word more succinctly, but it's the same" 12 | ;; (= __ @the-world) 13 | 14 | ;; "You can be the change you wish to see in the world." 15 | ;; (= __ (do 16 | ;; (dosync (ref-set the-world "better")) 17 | ;; @the-world)) 18 | 19 | ;; "Alter where you need not replace" 20 | ;; (= __ (let [exclamator (fn [x] (str x "!"))] 21 | ;; (dosync 22 | ;; (alter the-world exclamator) 23 | ;; (alter the-world exclamator) 24 | ;; (alter the-world exclamator)) 25 | ;; @the-world)) 26 | 27 | ;; "Don't forget to do your work in a transaction!" 28 | ;; (= 0 (do __ 29 | ;; @the-world)) 30 | 31 | ;; "Functions passed to alter may depend on the data in the ref" 32 | ;; (= 20 (do 33 | ;; (dosync (alter the-world ___)))) 34 | 35 | ;; "Two worlds are better than one" 36 | ;; (= ["Real Jerry" "Bizarro Jerry"] 37 | ;; (do 38 | ;; (dosync 39 | ;; (ref-set the-world {}) 40 | ;; (alter the-world assoc :jerry "Real Jerry") 41 | ;; (alter bizarro-world assoc :jerry "Bizarro Jerry") 42 | ;; __))) 43 | ) 44 | -------------------------------------------------------------------------------- /materials/section-2/README.md: -------------------------------------------------------------------------------- 1 | # Backend Programming 2 | 3 | ## Table of Contents 4 | 5 | 1. [Intro](#intro) 6 | 2. [Ring](#ring) 7 | 3. [Compojure](#compojure) 8 | 4. [Getting Started](#getting-started) 9 | 5. [Tasks](#tasks) 10 | 11 | 12 | ## Intro 13 | 14 | In this section we will delve into backend programming with Ring and Compojure. Compojure is a lightweight routing library built on top of Ring which is an http library for Clojure backend development. 15 | 16 | Breakdown: 17 | 18 | - Ring -> Http server abstraction ([docs](https://github.com/ring-clojure/ring/wiki)) 19 | - Compojure -> Routing lib ([docs](https://github.com/weavejester/compojure/wiki)) 20 | 21 | ## Ring 22 | 23 | As mentioned already, Ring is a library for building web applications. It is arguably the de facto choice for writing web applications in Clojure. Ring lets us write web apps with simple Clojure functions and maps, it also comes equipped with a auto-reloading development server. 24 | Ring is very lightweight, it only consists of four parts: 25 | 26 | - Handlers 27 | - Requests 28 | - Responses 29 | - Middlewares 30 | 31 | #### Handlers 32 | 33 | Handlers are functions that represent how your web app is defined. Handlers are always passed a Ring requests map. 34 | 35 | Example: 36 | ```clojure 37 | (defn hello-world [request] 38 | {:status 200 39 | :headers {"Content-Type" "text/plain"} 40 | :body "Hello, World!"}) 41 | ``` 42 | 43 | Handlers return maps (Ring responses) that Ring translates into HTTP responses. 44 | 45 | #### Response 46 | 47 | The response map that handlers return has a predefined set of keys. 48 | 49 | - `:status` - Http status code 50 | 51 | - `:headers` - A map containing the headers names as keys and header values as values inside the map 52 | 53 | - `:body` - The response body. The body can be of four types: `String`, `ISeq`, `File`, `InputStream`. 54 | *Note*\* *Maps aren't sequences!* 55 | 56 | #### Request 57 | 58 | Requests, as mentioned above, are always passed to handlers. Requests have a predefined set of keys: `:uri`, `:headers`, `:body`, `:query-string`, `:request-method`, and many more. 59 | 60 | Example of a request map: 61 | 62 | ```clojure 63 | {:uri "/home" 64 | :headers {"Content-Type" "text/plain"} 65 | :query-string "?user=adam" 66 | :request-method :get} 67 | ``` 68 | 69 | #### Middlewares 70 | 71 | Middlewares are small functions that exist between handlers and requests. A middleware can add functionality to a handler by either, manipulating the request before it is passed to the handler, or by manipulating the response before Ring translates it to a HTTP response. 72 | 73 | To get a better understanding on how Ring works and better examples on how Ring's four concepts work, please read Ring's documentation: 74 | 75 | https://github.com/ring-clojure/ring/wiki/Concepts 76 | 77 | ## Compojure 78 | 79 | Compojure is a routing library built on top of Ring. Compojure makes writing routes a breeze. That's actually all you need to know at this point about Compojure. Compojure's routes will be covered in greater detail later on in this section. 80 | 81 | If interested in learning more about Compojure, please read its documentation: 82 | 83 | https://github.com/weavejester/compojure/wiki 84 | 85 | ## Getting Started 86 | 87 | We will be generating a *lein* Compojure project in this section. The *lein* Compojure project template comes included with Ring. 88 | To scaffold a Compojure project, run `lein new compojure ` (replace name with a the name of your project). This command will create a Compojure project. You can already start developing your Compojure porject, just run `lein ring server` inside the project root and development server should start up. However, before we get started with developing our project, we should probably go through what the project actually looks like. The project has the following structure: 89 | 90 | ``` 91 | / 92 | ├── README.md 93 | ├── project.clj 94 | ├── resources 95 | │   └── public 96 | ├── src 97 | │   └── 98 | │   └── handler.clj 99 | └── test 100 | └── 101 | └── handler_test.clj 102 | ``` 103 | 104 | The most interesting files are without a doubt, `project.clj` and `src//handler.clj`. The `project.clj` file is the file that describes the project to Leiningen. The project dependencies, name, description etc. are described inside the `project.clj` file. In the case of a Compojure project, it also defines the entry point for the web-server: `:ring {:handler .handler/app}`. This tells Ring where to look for the entrypoint so it can bootstrap the server. The second interesting file is the `handler.clj` file. This is where the application is bootstrapped. It will look something like this: 105 | 106 | ```clojure 107 | (ns .handler 108 | (:require [compojure.core :refer :all] 109 | [compojure.route :as route] 110 | [ring.middleware.defaults :refer [wrap-defaults site-defaults]])) 111 | 112 | (defroutes app-routes 113 | (GET "/" [] "Hello World") 114 | (route/not-found "Not Found")) 115 | 116 | (def app 117 | (wrap-defaults app-routes site-defaults)) 118 | ``` 119 | 120 | Small breakdown: 121 | 122 | ```clojure 123 | (ns .handler 124 | (:require [compojure.core :refer :all] 125 | [compojure.route :as route] 126 | [ring.middleware.defaults :refer [wrap-defaults site-defaults]])) 127 | ``` 128 | 129 | Our namespace is defined with the *ns* macro which also allows us to import other namespaces into our current namespace. 130 | 131 | ```clojure 132 | (:require [compojure.core :refer :all]) 133 | ``` 134 | 135 | `:require` takes an *n* amount of vectors as arguments. The first value inside each vector should be a new unique namespace name. 136 | You can also define, with special keywords, which part of a namespace you want to include. 137 | 138 | - `:refer :all` -> Give me everything 139 | - `:as` -> Use namespace with a different name 140 | - `:refer [wrap-defaults site-defaults]` -> Only require `wrap-defaults` and `site-defaults` from the namespace. 141 | 142 | In our project, we include Compojure core, the Compojure route namespace, along with the middleware namespace from Ring. Middlewares, as mentioned before, are just an easy way to handle requests before they reach your route handler. Ring has a few default ones which are used in the Compojure template. 143 | 144 | ```clojure 145 | (defroutes app-routes 146 | (GET "/" [] "Hello World") 147 | (route/not-found "Not Found")) 148 | ``` 149 | 150 | `defroutes` is a macro provided by Compojure. It takes two parameters. A name and Compojure route handlers. A route handler is nothing more than a basic function. Compojure provides a handful of route handlers. `GET`, `POST`, `PATCH`, `PUT`, `HEAD`, `DELETE`, `OPTIONS` and `ANY`. 151 | All of these route handlers work in the same way. Each of them take a path as the first argument, a request body, and a route body as the last argument. 152 | 153 | ```clojure 154 | (GET "/home" request (str "Welcome Home " (:person (:params request)) "!")) 155 | ^ ^ ^ ^ 156 | method route request body 157 | ``` 158 | 159 | The request body (Ring request) is quite unique. Compojure will compile the request differently depending on what you have given it as the second argument. This way of defining routes can be a bit unclear, as by default *all request params are always strings and not keywords*, and fetching the params key from the request object can be very tiring and lead to verbose code. The most common practice is to pass it a vector. Compojure will automatically destruct the request object and pass the request parameters present in the vector into your route body. 160 | 161 | ```clojure 162 | (GET "/home" [person] (str "Welcome Home " person "!")) 163 | ^ ^ ^ ^ 164 | method route params body 165 | ``` 166 | 167 | This is the more common approach when writing routes. 168 | 169 | Compojure will automatically transform the result of your route handler into a Ring response. You can either return a normal Ring response map or string from your route handler. In the case of a string, Compojure will add the result to the `:body` of the Ring response map. 170 | 171 | The last part: 172 | 173 | ```clojure 174 | (def app 175 | (wrap-defaults app-routes site-defaults)) 176 | ``` 177 | 178 | This is the bootstrapped application. Ring will look for this _var_ when starting the app. The app _var_ can be wrapped in middleware layers. Compojure wraps our routes with some default middlewares provided by Ring. 179 | 180 | The default middlewares, such as `site-default`, add a lot of opinionated boilerplate features to your app, e.g. ssl redirect, cookie support etc. We are not going to be using these features, so we won't be using these default middlewares. We will be using json specific middlewares in our project, _so you can go ahead and delete the default middlewares._ 181 | 182 | ```clojure 183 | (def app app-routes) 184 | ``` 185 | 186 | This is how it should look like now that you've deleted the middlewares. 187 | 188 | Since we are building a basic REST API, we will need to be able to provide json responses. This is done by adding the 189 | `ring.middleware.json` dependency to the project. 190 | 191 | Adding new dependencies to your Clojure project is quite simple. Simply open your `project.clj` file and add the dependency to the list of dependencies. In this case we want to add `[ring/ring-json "0.4.0"]`. `ring.middleware.json` exports one function that we want to use, `wrap-json-response`. This function will transform a Clojure map into a json object and it will also add `Content-Type: application/json; charset=utf-8` header to the response. 192 | 193 | A more pragmatic explanation. *This middleware will allow us the return Clojure maps from our routes instead of strings*. 194 | 195 | Now we can wrap our app with the json middleware: 196 | 197 | ```clojure 198 | (ns .handler 199 | (:require [compojure.core :refer :all] 200 | [compojure.route :as route] 201 | [ring.middleware.json :refer [wrap-json-response]])) 202 | ... 203 | (def app (wrap-json-response app-routes)) 204 | ``` 205 | 206 | An other middleware that needs to be added before we can begin is `ring.middleware.params`. This middleware exposes a middleware function, `wrap-params`, that parses url-encoded parameters from the query string and request body, Ring does not do this by default. This middleware is already included in Ring, so it _does not have to be added to the_ `project.clj` _dependencies list!_ 207 | 208 | ```clojure 209 | (ns .handler 210 | (:require [compojure.core :refer :all] 211 | [compojure.route :as route] 212 | [ring.middleware.params :refer [wrap-params]] 213 | [ring.middleware.json :refer [wrap-json-response]])) 214 | ... 215 | (def app (-> app-routes 216 | wrap-params 217 | wrap-json-response)) 218 | ``` 219 | 220 | We can now test our setup. 221 | 222 | ```clojure 223 | (GET "/profile" [person] {:person person}) 224 | ``` 225 | 226 | While testing this, you might have noticed that nothing gets returned from our API. 227 | Why is that? Ring expects that all routes, even the ones created by Compojure, return a Ring response. As mentioned earlier, a Ring response looks like this: 228 | 229 | ```clojure 230 | {:status 200 231 | :headers {"Content-Type" "text/plain"} 232 | :body "Hello World!"} 233 | ``` 234 | 235 | So, how do we fix this? As mentioned before, we can either return a string or a Ring response map from our route handler. Currently the `:body` is `nil` because we are returning an invalid Ring response. We need to wrap our response in a valid Ring response map, like this: 236 | ```clojure 237 | (GET "/profile" [person] {:status 200 238 | :body {:person person}}) 239 | ``` 240 | 241 | But this isn't really an elegant solution. Luckly, Ring provides a helper function that creates this response map for us. Inside the `ring.util.response` namespace, there's a function conveniently called `response`. Once you've included the `ring.util.response` namespace, you can refactor your handler to look something like this: 242 | 243 | ```clojure 244 | (ns .handler 245 | (:require [compojure.core :refer :all] 246 | [compojure.route :as route] 247 | [ring.middleware.params :refer [wrap-params]] 248 | [ring.util.response :refer [response]] 249 | [ring.middleware.json :refer [wrap-json-response]])) 250 | ... 251 | (GET "/profile" [person] (response {:person person})) 252 | ``` 253 | 254 | Now everything is set up and we can begin building the rest our REST API. 255 | In the next section you will finally get to do some coding yourself, i.e. the tasks are next! Good luck! 256 | 257 | --- 258 | 259 | ## Task 260 | 261 | #### TODO REST API 262 | 263 | The task for this section is to create REST API for a TODO application. 264 | _The TODOs should be stored inside an_ `atom`. The REST API should also provide four endpoints. 265 | 266 | 1. `GET` Should list all available TODOs. 267 | 2. `POST` Should add a single TODO. 268 | *Tips* this endpoint doesn't need to take anything else than a name query parameter. 269 | If you want to send a json body instead, you need to add an extra middleware, `wrap-json-params`. This middleware is included in the `ring.middleware.json` namespace. 270 | 271 | 3. `DELETE` Should delete a TODO. 272 | 4. `PATCH` Should update a single TODO - mark as _done_. 273 | 274 | A TODO could look something like this: 275 | 276 | ```clojure 277 | (def todo 278 | "A basic TODO" 279 | {:id 1 280 | :name "Wash the dishes" 281 | :done false}) 282 | ``` 283 | 284 | And the list should look like this: 285 | 286 | ```clojure 287 | (def todos (atom [])) 288 | ``` 289 | 290 | Extra credit: 291 | 292 | 1. Make the `GET` endpoint accept a query parameter, _sortby_, which sorts the TODOs either in ascending or descending order, or maybe even in some other way..? 293 | 294 | 2. Make the delete endpoint return `400 Bad Request` if the specified TODO doesn't exist in the TODO list. Hint* `ring.util.response` has a function called `status` 295 | 296 | 3. Make a new endpoint that empties the TODO list. 297 | 298 | 4. Make an new endpoint which only deletes TODOs, which id's are powers of 2. 299 | -------------------------------------------------------------------------------- /materials/section-3/README.md: -------------------------------------------------------------------------------- 1 | # Section 3: Writing a frontend web application with ClojureScript and Reagent 2 | 3 | ### Contents 4 | 5 | 1. [Introduction to ClojureScript](#1-introduction-to-clojurescript) 6 | 2. [Introduction to Reagent](#2-introduction-to-reagent) 7 | 3. [Create a new Reagent project](#3-create-a-new-reagent-project) 8 | 4. [Create the views](#4-create-the-views) 9 | 5. [Introduction to Ajax on ClojureScript](#5-introduction-to-ajax-on-clojurescript) 10 | 6. [Create the backend calls](#6-create-the-backend-calls) 11 | 12 | ## 1. Introduction to ClojureScript 13 | 14 | ### What is ClojureScript? 15 | 16 | Taken directly from the ClojureScript [website](https://clojurescript.org): 17 | 18 | > ClojureScript is a compiler for Clojure that targets JavaScript. It emits JavaScript code which is compatible with the advanced compilation mode of the Google Closure optimizing compiler. 19 | 20 | The Google Closure compiler allows for your ClojureScript code to be compiled into highly optimised JavaScript code. It (in it's own words) "compiles from JavaScript to better JavaScript". It also removes dead code. 21 | 22 | 23 | ### So, why not just use JavaScript? 24 | 25 | Unsurprisingly, the vast majority of the benefits of using Clojure also apply to using ClojureScript. So, you get all the parts of using a functional lisp dialect with immutable-by-default data structures. There's also some great tooling specific to ClojureScript that provides additional help to the developer experience. One such tool is [Figwheel](https://github.com/bhauman/lein-figwheel), which handles your build-related tasks such as hot code reloading, dependency management, loval dev environment management, and even provides you with a REPL that hooks directly into your running application (which is _awesome_). 26 | 27 | One additional benefit that isn't to be underestimated is using a single language across the full stack. One particularly nice part of this (which sadly we won't be utilising within this workshop) is the concept of [Reader Conditionals](https://clojure.org/reference/reader#_reader_conditionals). This allows you to define `cljc` files in your project that work with both Clojure _and_ ClojureScript. For example, here's a basic project structure of a full-stack Clojure(Script) application: 28 | 29 | ``` 30 | src 31 | ├── clj 32 | │   └── 33 | │   ├── core.clj 34 | │   ├── handler.clj 35 | │   ├── profiles.clj 36 | │   └── server.clj 37 | ├── cljs 38 | │   └── 39 | │   ├── core.cljs 40 | │   ├── db.cljs 41 | │   ├── events.cljs 42 | │   ├── routes.cljs 43 | │   ├── subs.cljs 44 | │   └── views.cljs 45 | └── cljc 46 | └── 47 | ├── utils.cljc 48 | └── other.cljc 49 | ``` 50 | 51 | Here, everything defined in `utils.cljc` and `other.cljc` can be refereced in our `clj` and `cljs` files. This makes it possible to create libraries that target both Clojure and ClojureScript! 52 | 53 | ### What are the differences between Clojure and ClojureScript? 54 | 55 | There are a few differences here (but actually surpsingly little on the whole), and if you're really interested I'd recommend you go to the page on the [ClojureScript website](https://clojurescript.org/about/differences) that talks about this in a little bit more detail. But, to summarise the main bits: 56 | 57 | - Data Structures 58 | - `nil` in ClojureScript corresponds to `null` and `undefined` in JavaScript, as opposed to just `null` in Java. 59 | - Characters do not exist in JavaScript, so a ClojureScript `\a` corresponds to the single letter string `"a"` in JavaScript 60 | - Refs do not exist in ClojureScript (but we can just use Atoms instead, which work exactly the same as they do in Clojure) 61 | - ClojureScript only supports integer and floating point literals that can be mapped to JavaScript primitives. 62 | - Therefore, Ratio, BigDecimal & BigInteger do not exist in ClojureScript. 63 | - Equality on numbers works as it does in JavaScript, and not in Clojure. 64 | - `(= 1.0 1) => true` in ClojureScript, but not in Clojure! 65 | 66 | For a quick reference of functions available in ClojureScript code, check the [ClojureScript cheat sheet](https://cljs.info/cheatsheet/). 67 | 68 | ### Interop between ClojureScript and JavaScript 69 | 70 | When working in ClojureScript, we are often working with ClojureScript's own data structures alongside native JavaScript data structures. So naturally, we are often going to be performing operations that allow us to convert JavaScript objects and primitives to ClojureScript, and vice-versa. The following functions and macros allow us to do this easily. To help us with this, ClojureScript defines a namespace called `js` that allows us to access JavaScript objects, functions and primitives defined in the global scope. 71 | 72 | ```clojure 73 | (def text js/globalName) 74 | ``` 75 | 76 | If we want to invoke JavaScript functions, we can do so by prefixing the function name with a `.`. We can pass function arguments at the end of each form. 77 | 78 | ```clojure 79 | (.getElementById js/document "my-element") 80 | ``` 81 | 82 | ClojureScript data structures (set, vector, list, keyword, symbol and map) need to be converted in order to use them in JavaScript. The same applies also vice versa. 83 | 84 | You can also access properties of a JavaScript object directly from ClojureScript with the `.-` property access syntax. 85 | 86 | ```clojure 87 | (.-bar js/foo) ;; JS output: foo.bar; 88 | ``` 89 | 90 | You can access nested properties using the `..` access syntax. 91 | 92 | ```clojure 93 | (fn [e] 94 | (.. e -target -value)) 95 | ;; JS output: function(e) { return e.target.value; } 96 | ``` 97 | 98 | We can create JavaScript objects in ClojureScript using the `js-obj` macro: 99 | 100 | ```clojure 101 | (js-obj "foo" "bar") ;; creates { foo: "bar"} 102 | ``` 103 | 104 | This only allows you to use basic data literals as values, though. Any ClojureScript data structures (such as vectors, hash sets etc) won't be changed. To recursively change values into JavaScript, we can use the `clj->js` function: 105 | 106 | ```clojure 107 | (clj->js :a [1 2 3] :b #{"wow" "what" "fun"}) 108 | ``` 109 | 110 | This will correspond to 111 | 112 | ```javascript 113 | { 114 | "a": [1, 2, 3], 115 | "b": ["wow", "what", "fun"] 116 | } 117 | ``` 118 | 119 | If we want to do the same, but in reverse (recursively transform JavaScript into ClojureScript maps), we can do this in such a way using the `js->clj` function: 120 | 121 | ```clojure 122 | (def js-array (js-obj "foo" "bar" "awesome" true)) 123 | (js->clj js-array :keywordize-keys true) ;; => {:foo "bar", :awesome true} 124 | ``` 125 | As you can see above, we can add a flag to automatically keywordise the JavaScript object keys, if we would like. 126 | 127 | If you'd like to read a little more in depth, I'd recommend turning to [this blog post](https://www.spacjer.com/blog/2014/09/12/clojurescript-javascript-interop/) which provides a great rundown of additional ways you can interact with JS from ClojureScript, and vice-versa. 128 | 129 | ### Using JavaScript dependencies 130 | 131 | To use JavaScript dependencies, you can use either [CLJSJS](http://cljsjs.github.io/) or [shadow-cljs](https://github.com/thheller/shadow-cljs). CLJSJS contains JavaScript libraries that are packaged as ClojureScript modules. 132 | 133 | Shadow-cljs is an alternative tool to compile ClojureScript and manage the dependencies. It requires NPM, Node and Java SDK. It can target browser, node, npm-module, react-native and chrome-extension. In addition to *seamless npm integration*, it offers fast builds, caching, live reload (CLJS + CSS), CLJS REPL and code splitting. 134 | 135 | --- 136 | 137 | ## 2. Introduction to Reagent 138 | 139 | Reagent is a minimalistic interface between ClojureScript and React, and we will be using this today as the base of our frontend. It plays into ClojureScripts strengths by using just ClojureScript data and functions to define React components (rather than JSX sticking out like a sore thumb in the middle of some JavaScript code). 140 | 141 | The best place to learn the basics of Reagent is by going to the [official documentation](https://reagent-project.github.io/) and having a read through. You only need to read up until the end of the "Managing state in Reagent" section for the scope of this workshop, but you're more than welcome to continue reading if you have the time. 142 | 143 | Once you've read through and understood the basics of Reagent, let's continue by making our frontend project. 144 | 145 | --- 146 | 147 | ## 3. Create a new Reagent project 148 | 149 | Create the project using the [reagent-frontend](https://github.com/reagent-project/reagent-frontend-template) template 150 | 151 | $ cd materials/section-3 152 | $ lein new reagent-frontend todoapp 153 | 154 | Start it 155 | 156 | $ lein figwheel 157 | 158 | Now you have an independent ClojureScript & Reagent frontend running on a Figwheel development server and a *ClojureScript* REPL. 159 | 160 | The REPL is connected to your browser and you can directly require namespaces, call functions and even change atom values in the REPL. 161 | 162 | ### Application structure 163 | 164 | ``` 165 | . 166 | ├── LICENSE 167 | ├── README.md 168 | ├── env 169 | │   ├── dev 170 | │   │   ├── clj 171 | │   │   │   └── user.clj 172 | │   │   └── cljs 173 | │   │   └── todoapp 174 | │   │   └── dev.cljs 175 | │   └── prod 176 | │   └── cljs 177 | │   └── todoapp 178 | │   └── prod.cljs 179 | ├── project.clj 180 | ├── public 181 | │   ├── css 182 | │   │   └── site.css 183 | │   ├── index.html 184 | │   └── js 185 | └── src 186 | └── todoapp 187 | └── core.cljs 188 | ``` 189 | 190 | - `env` contains bootstrap code for development and production 191 | - `project.clj` dependencies 192 | - `public` static resources, ClojureScript code will be compiled in `public/js` 193 | - `src` the ClojureScript sources 194 | 195 | ### Full stack Clojure project 196 | 197 | Often, you would start by creating a full stack Clojure/ClojureScript/Reagent project with `lein new reagent foobar` command. 198 | 199 | The full stack project has one `project.clj` that defines dependencies and build for both backend and frontend and it starts only one http development server that serves both backend and static frontend files. 200 | 201 | The [full stack Reagent template](https://github.com/reagent-project/reagent-template) contains also flags to enable e.g. unit tests and less/sass compilation. 202 | 203 | --- 204 | 205 | ## 4. Create the views 206 | 207 | Create the application global state in atoms, e.g. a vector of maps containing todo data: 208 | 209 | ``` 210 | (def todos 211 | (r/atom 212 | [{:id 1 :name "Write frontend" :done false} 213 | {:id 2 :name "Test it" :done false}])) 214 | ``` 215 | 216 | Write a Reagent component for rendering a single todo item. When you iterate over the vector, remember the `^{:key ...}` in the for comprehension. 217 | 218 | Write the rest of the components: adding a new todo item, toggling the done state, and removing a todo. 219 | 220 | After you have tested that the components work, add the functions that take care of the `on-change` and `on-click` interaction. 221 | 222 | --- 223 | 224 | ## 5. Introduction to Ajax on ClojureScript 225 | 226 | There's two libraries that provide HTTP calls on ClojureScript: 227 | - [cljs-ajax](https://github.com/JulianBirch/cljs-ajax) which offers a callback style interface 228 | - [cljs-http](https://github.com/r0man/cljs-http) which utilizes [core.async](https://github.com/clojure/core.async) channels and macros 229 | 230 | We wanted to keep things simple and not go through core.async, so we used `cljs-ajax`. Let's see how it works: https://github.com/JulianBirch/cljs-ajax 231 | 232 | The `cljs-ajax` library takes care of `json` parsing. To handle the response formatting, use the following options: 233 | 234 | ```clojure 235 | {:response-format :json 236 | :keywords? true} 237 | ``` 238 | 239 | To handle the POST parameter formatting, give the parameters as a Clojure map and use the following option: 240 | 241 | ```clojure 242 | {:format :json} 243 | ``` 244 | 245 | ### CORS headers 246 | 247 | Before creating the frontend, add the CORS header middleware in the backend project: 248 | 249 | 1. In `project.clj`, add the following dependency: `[ring-cors "0.1.13"]` 250 | 2. In `handler.clj`, require: `[ring.middleware.cors :refer [wrap-cors]]` 251 | 3. In `handler.clj`, use the following middleware (inside `def app`): 252 | 253 | ```clojure 254 | (wrap-cors :access-control-allow-origin [#"http://localhost:3449"] 255 | :access-control-allow-methods [:get :patch :post :delete]) 256 | ``` 257 | 258 | --- 259 | 260 | ## 6. Create the backend calls 261 | 262 | Use `cljs-ajax` to create the backend calls. The way to refactor your implementation would be: 263 | 1. Instead of mutating the `todos` atom locally, make an Ajax call. Update the `todos` state atom in the handler function. 264 | 2. When the page is loaded, fetch the current state. This could be called in the `core/mount-root` function. 265 | -------------------------------------------------------------------------------- /materials/section-4/README.md: -------------------------------------------------------------------------------- 1 | # Database (Extra Credit) 2 | 3 | In this extra credit section we will add a database and integrate it with our app. Storing state in a atom is perfectly fine if you don't want to have persistent data, otherwise you would probably need a datastore or database. We will be using _postgres_ as our database. We won't be talking about specific databases in this section, only how you integrate with them (in this case postgres). 4 | 5 | ### Prerequisite 6 | 7 | For this section, you will need docker installed on your machine. Follow [this](https://runnable.com/docker/getting-started/) guide to get started with docker. We won't be discussing docker in any great detail in this section, as this section only explains how to integrate with a database. 8 | 9 | ### Setup 10 | 11 | Once you have docker installed, create a `Dockerfile` inside the `section-2` folder. The `Dockerfile` should look like this: 12 | 13 | ``` 14 | FROM postgres:10 15 | ENV POSTGRES_DB clojure_workshop_db 16 | ADD create_table.sql /docker-entrypoint-initdb.d/ 17 | ``` 18 | 19 | On the last line we copy a sql file (create_table.sql) into our container. This file will create our database table. The file should look like this: 20 | 21 | ```sql 22 | CREATE TABLE todo( 23 | id serial PRIMARY KEY NOT NULL, 24 | name varchar(50) NOT NULL, 25 | done bool DEFAULT FALSE 26 | ); 27 | ``` 28 | 29 | Next, we will build and run our database. 30 | Run `docker build -t clojure_workshop_db .` to build the docker image of our database. Next, run `docker run -p 5432:5432 clojure_workshop_db`. 31 | Now you should have a database up and running! 32 | 33 | ### HugSQL & Postgres 34 | 35 | We will be using *postgres* and *hugsql* for our database integration. We will add two dependencies `[com.layerware/hugsql "0.4.9"]` and `[org.postgresql/postgresql "42.2.2"]`. The first one is *hugsql*. *Hugsql* is a library for writing SQL in Clojure. 36 | 37 | >SQL is the right tool for the job when working with a relational database! 38 | HugSQL uses simple conventions in your SQL files to define (at compile time) database functions in your Clojure namespace, creating a clean separation of Clojure and SQL code. 39 | HugSQL supports runtime replacement of SQL Value Parameters (e.g., where id = :id), SQL Identifiers (i.e. table/column names), and SQL Keywords. You can also implement your own parameter types. 40 | HugSQL features Clojure Expressions and Snippets providing the full expressiveness of Clojure and the composability of partial SQL statements when constructing complex SQL queries. 41 | HugSQL has protocol-based adapters supporting multiple database libraries and ships with adapters for clojure.java.jdbc (default) and clojure.jdbc 42 | 43 | - HugSQL 44 | 45 | The second library we add is the jdbc driver for postgres. HugSQL uses jdbc and requires a jdbc driver for a database to work. Jdbc (Java database connectivity) is a Java API that let's you communicate with databases. 46 | 47 | ### Using HugSQL 48 | 49 | To get started using Hugsql, you will actually need to write a sql file. Hugsql transforms basic sql queries into Clojure functions. It does this with the `def-db-fns` macro. `def-db-fns` will take a sql file location as a parameter. 50 | 51 | ```clojure 52 | (def-db-fns "") 53 | ``` 54 | The sql file could look something like this: 55 | 56 | ```sql 57 | -- :name fetch-todos :? :* 58 | SELECT * FROM todo; 59 | ``` 60 | 61 | The comment above the sql query might not look familiar. This is Hugsql specific. Let's dissect the comment to see what is actually happening. This comment is called the command sequence. 62 | 63 | 1. `:name fetch-todos` => The Clojure function name given to this query. Note! The functions will only be available inside the current namespace where the `def-db-fns` function is called in. The named function should *always be passed the database connection url* as the first argument. 64 | Usage: 65 | ```clojure 66 | (ns myproject.namespace 67 | (:require [hugsql.core :refer [def-db-fns]])) 68 | 69 | (def db-url "postgresql://postgres@localhost:5432/clojure_workshop_db") 70 | 71 | ;; Replace with actual name of your project 72 | (def-db-fns "/queries.sql") 73 | 74 | (defn todos [] 75 | (fetch-todos db-url)) 76 | ``` 77 | 78 | 2. `:?` => indicates that we are about to run query with an expected result set. This is the default behaviour of Hugsql. 79 | 80 | 3. `:*` => tells Hugsql that we are expecting many rows to be returned by this query and they should be returned as a vector. If no rows are returned, the vector will be empty. 81 | 82 | Hugsql is a huge library, and to explain it in greater detail would be a disservice to it's great documentation. Checkout the [documentation](https://www.hugsql.org/#detail) for more information on how to use Hugsql. 83 | 84 | 85 | ### Tasks 86 | 87 | There will only be one task in this section: Remove the todo atom and store all the todos in the database instead. 88 | -------------------------------------------------------------------------------- /materials/section-5/README.md: -------------------------------------------------------------------------------- 1 | # WIP 2 | 3 | ## Deployment (Heroku) 4 | --------------------------------------------------------------------------------