You're reading from The Clojure Workshop
1. Hello REPL!
Activity 1.01: Performing Basic Operations
Solution:
- Open the REPL.
- Print the message
"I am not afraid of parentheses"
to motivate yourself:user=> (println "I am not afraid of parentheses") I am not afraid of parentheses nil
- Add 1, 2, and 3 and multiply the result by 10 minus 3, which corresponds to the following
infix
notation: (1 + 2 + 3) * (10 - 3):user=> (* (+ 1 2 3) (- 10 3)) 42
- Print the message
"Well done!"
to congratulate yourself:user=> (println "Well done!") Well done! Nil
- Exit the REPL by pressing Ctrl + D or typing the following command:
user=> (System/exit 0)
By completing this activity, you have written code that prints a message to the standard output. You have also performed some mathematical operations using the prefix notation and nested expressions.
Activity 1.02: Predicting the Atmospheric Carbon Dioxide Level
Solution:
- Open your favorite editor and...
2. Data Types and Immutability
Activity 2.01: Creating a Simple In-Memory Database
Solution:
- First, create the helper functions. You can get the Hash Map by executing the
read-db
function with no arguments, and write to the database by executing thewrite-db
function with a Hash Map as an argument:user=> (def memory-db (atom {})) #'user/memory-db (defn read-db [] @memory-db) #'user/read-db user=> (defn write-db [new-db] (reset! memory-db new-db)) #'user/write-db
- Start by creating the
create-table
function. This function should take one parameter: the table name. It should add a new key (the table name) at the root of our Hash Map database, and the value should be another Hash Map containing two entries – an empty vector at thedata
key and an empty Hash Map at theindexes
key:user=> (defn create-table [table-name] (let [db (read-db)] (write-db (assoc db table-name {:data [] :indexes {}}))...
3. Functions in Depth
Activity 3.01: Building a Distance and Cost Calculator
Solution:
- Start by defining the
walking-speed
anddriving-speed
constants:(def walking-speed 4) (def driving-speed 70)
- Create two other constants representing two locations with the coordinates
:lat
and:lon
. You can use the previous example with Paris and Bordeaux or look up your own. You will be using them to test your distance and itinerary functions:(def paris {:lat 48.856483 :lon 2.352413}) (def bordeaux {:lat 44.834999 :lon -0.575490})
- Create the distance function. It should take two parameters representing the two locations for which we need to calculate the distance. You can use a combination of sequential and associative destructuring right in the function parameters to disassemble the latitude and longitude from both locations. You can decompose the steps of the calculation in a
let
expression and use theMath/cos
function to calculate the cosine andMath/sqrt
to calculate...
4. Mapping and Filtering
Activity 4.01: Using map and filter to Report Summary Information
Solution:
- To start, set up a simple framework using the
->>
threading macro:(defn max-value-by-status [field status users] (->> users ;; code will go here ))
This defines the fundamental structure of our function, which we can sum up as follows: start with
users
and send it through a series of transformations. - The first of these transformations will be to filter out all the users that don't have the status we are looking for. We'll use
filter
for that, naturally, and we'll include a...
5. Many to One: Reducing
Activity 5.01: Calculating Elo Ratings for Tennis
Solution:
- Here is the minimal
deps.edn
file you'll need:{:deps {org.clojure/data.csv {:mvn/version "0.1.4"} semantic-csv {:mvn/version "0.2.1-alpha1"} org.clojure/math.numeric-tower {:mvn/version "0.0.4"}}}
- Here is the corresponding namespace declaration:
(ns packt-clj.elo (:require [clojure.math.numeric-tower :as math] [clojure.java.io :as io] [clojure.data.csv :as csv] [semantic-csv.core :as sc])
- For the overall structure of your function, we will follow the same patterns we've used so far: a
with-open
macro with some pre-processing code:(defn elo-world ([csv k] (with-open...
6. Recursion and Looping
Activity 6.01: Generating HTML from Clojure Vectors
Solution:
- We'll use
clojure.string
in our solution. It is not strictly necessary, butclojure.string/join
is a very convenient function. Becauseclojure.string
is a standard Clojure namespace, adeps.edn
file containing only an empty map is sufficient for this activity:(ns my-hiccup (:require [clojure.string :as string]))
- Here are the smaller functions that we'll use later in the main HTML-producing function:
(defn attributes [m] (clojure.string/join " " (map (fn [[k v]] (if (string? v) (str (name k) "=\"" v "\"") ...
7. Recursion II: Lazy Sequences
Activity 7.01: Historical, Player-Centric Elo
Solution:
- Set up your project, which should be based on the code written for the last exercises in this chapter.
- The solution follows the pattern established with
take-matches
. Let's start with the parameters. We need to define separate behaviors for matches played by the "focus player" and matches played between other players. The first thing we need is, of course, a way to identify the player, so we'll add aplayer-slug
argument. This wasn't necessary intake-matches
because there we treated all the matches the same, regardless of who played in them.In
take-matches
, we had alimit
argument to control how deeply we walked the tree. In this case, we need two different parameters, which we will callfocus-depth
andopponent-depth
. Together, that gives us the following parameters for our newfocus-history
function:(defn focus-history [tree player-slug focus-depth opponent...
8. Namespaces, Libraries and Leiningen
Activity 8.01: Altering the Users List in an Application
- Import the
clojure.string
namespace withuse
and the:rename
keyword for thereplace
andreverse
functions:(use '[clojure.string :rename {replace str-replace, reverse str-reverse}])
- Create a set of users:
(def users #{"mr_paul smith" "dr_john blake" "miss_katie hudson"})
- Replace the underscore between honorifics and first names:
(map #(str-replace % #"_" " ") users)
This will return the following:
("mr paul smith" "miss katie hudson" "dr john blake")
- Use the
capitalize
function to capitalize each person's initials in the user group:(map #(capitalize %) users)
This will return the following:
("Mr_paul smith" "Miss_katie hudson" "Dr_john blake")
- Update the user list by using the string's
replace
andcapitalize
functions:(def updated-users...
9. Host Platform Interoperability with Java and JavaScript
Activity 9.01: Book-Ordering Application
Solution:
- Create a new project:
lein new app books-app
- Import the necessary namespaces:
(ns books-app.core (:require [books-app.utils :as utils]) (:import [java.util Scanner]) (:gen-class))
- Create a map to hold books by year:
(def ^:const books {:2019 {:clojure {:title "Hands-On Reactive Programming with Clojure" :price 20} :go {:title "Go Cookbook" :price 18}} :2018 {:clojure {:title "Clojure Microservices" :price 15} ...
10. Testing
Activity 10.01: Writing Tests for the Coffee-Ordering Application
Solution:
- Import the testing namespaces:
(ns coffee-app.utils-test (:require [clojure.test :refer :all] [coffee-app.core :refer [price-menu]] [coffee-app.utils :refer :all]))
- Create tests using the
clojure.test
library to display the orders messages. - Test the application using the
is
macro:(deftest display-order-test (testing "Multiple tests with is macro" (is (= (display-order {:number 4 :price 3.8 :type :latte}) "Bought 4 cups of latte for €3.8")) ...
11. Macros
Activity 11.01: A Tennis CSV Macro
Solution:
- Here is one possibility for the expanded code:
(with-open [reader (io/reader csv)] (->> (csv/read-csv reader) sc/mappify (sc/cast-with {:winner_games_won sc/->int :loser_games_won sc/->int}) (map #(assoc % :games_diff (- (:winner_games_won %) (:loser_games_won %)))) (filter #(> (:games_diff %) threshold)) (map #(select-keys % [:winner_name :loser_name :games_diff])) doall))
This should be taken as a rough sketch for the final output.
- Set up your project. The
deps.edn
file should look like this:{:deps &...
12. Concurrency
Activity 12.01: A DOM Whack-a-mole Game
Solution:
- Create a project with
lein figwheel
:lein new figwheel packt-clj.dom-whackamole -- --rum
- Move to the new
packt-clj.dom-whackamole
directory and start a ClojureScript REPL:lein figwheel
In your browser, at
localhost:3449/index.html
, you should see the default Figwheel page: - Open
dom-whackamole/src/packt-clj/dom-whackamole/core.cljs
in your editor or IDE. This is where you will write all the remaining code. - Define the atoms that will determine the game's state:
(def game-length-in-seconds 20) (def millis-remaining (atom (* game-length-in-seconds 1000))) (def points (atom 0)) (def game-state (atom :waiting)) (def clock-interval (atom nil)) (def moles (atom (into [] (repeat 5 {:status :waiting ...
13. Database Interaction and the Application Layer
Activity 13.01: Persisting Historic Tennis Results and ELO Calculations
Solution:
- In a new project, begin with the following dependencies:
{:deps {clojure.java-time {:mvn/version "0.3.2"} hikari-cp {:mvn/version "2.8.0"} org.apache.derby/derby {:mvn/version "10.14.2.0"} org.clojure/data.csv {:mvn/version "0.1.4"} org.clojure/java.jdbc {:mvn/version "0.7.9"} semantic-csv {:mvn/version "0.2.1-alpha1"}}}
- In our
src
directory, create the following namespaces:packt-clj.tennis.database packt-clj.tennis.elo packt-clj.tennis.ingest packt-clj.tennis.parse packt-clj.tennis.query
- Creating our connection pool in the database namespace...
14. HTTP with Ring
Activity 14.01: Exposing Historic Tennis Results and ELO Calculations via REST
Solution:
- Add the following dependencies to
packt-clj.tennis
in thedeps.edn
file:{:deps {.. clj-http {:mvn/version "3.10.0"} compojure {:mvn/version "1.6.1"} metosin/muuntaja {:mvn/version "0.6.4"} org.clojure/data.json {:mvn/version "0.2.6"} ring/ring-core {:mvn/version "1.7.1"} ring/ring-jetty-adapter {:mvn/version "1.7.1"}}
- Create our namespace with the following
require
route:(ns packt-clj.tennis.api (:require [clojure.edn :as edn] [compojure.core :refer [context defroutes...
15. The Frontend: A ClojureScript UI
Activity 15.01: Displaying a Grid of Images from the Internet
Solution:
- At the command-line prompt, create a new Figwheel project using the following Leiningen command:
lein new figwheel packt-clj.images -- --reagent
- Move to the
packt-clj.images/
directory and type:lein figwheel
After a few seconds, your browser should open to the default Figwheel page:
- Open the
src/packt_clj/images/core.cljs
file in your preferred editor and modify the code:(ns packt-clj.images.core (:require [reagent.core :as r]))
- The commonly used alias for Reagent is
r
instead of Reagent:(defonce app-state (r/atom {:images [] :author-display true...