├── README.md ├── code ├── 4clojure.md ├── fwpd │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── intro.md │ ├── project.clj │ ├── src │ │ └── fwpd │ │ │ └── core.clj │ ├── suspects.csv │ └── test │ │ └── fwpd │ │ └── core_test.clj ├── pegthing │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── intro.md │ ├── project.clj │ ├── src │ │ └── pegthing │ │ │ └── core.clj │ └── test │ │ └── pegthing │ │ └── core_test.clj ├── playsync │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── intro.md │ ├── project.clj │ ├── src │ │ └── playsync │ │ │ └── core.clj │ └── test │ │ └── playsync │ │ └── core_test.clj ├── scratch │ ├── .gitignore │ ├── 2008.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── intro.md │ ├── fips.json │ ├── project.clj │ ├── src │ │ └── scratch │ │ │ ├── core.clj │ │ │ ├── crime.clj │ │ │ └── rocket.clj │ └── test │ │ └── scratch │ │ ├── core_test.clj │ │ ├── crime_test.clj │ │ └── rocket_test.clj └── the-divine-cheese-code │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── doc │ └── intro.md │ ├── project.clj │ ├── src │ └── the_divine_cheese_code │ │ ├── core.clj │ │ └── visualization │ │ └── svg.clj │ └── test │ └── the_divine_cheese_code │ └── core_test.clj ├── functions.md ├── posts ├── 2020-06-18.md ├── 2020-06-22.md ├── 2020-06-23.md ├── 2020-06-24.md ├── 2020-06-25.md ├── 2020-06-26.md ├── 2020-06-27.md ├── 2020-06-28.md ├── 2020-06-29.md ├── 2020-06-30.md ├── 2020-07-01.md ├── 2020-07-02.md ├── 2020-07-03.md ├── 2020-07-04.md ├── 2020-07-05.md ├── 2020-07-06.md ├── 2020-07-07.md ├── 2020-07-08.md ├── 2020-07-09.md ├── 2020-07-10.md ├── 2020-07-11.md ├── 2020-07-12.md ├── 2020-07-13.md ├── 2020-07-14.md ├── 2020-07-15.md ├── 2020-07-16.md ├── 2020-07-17.md ├── 2020-07-18.md ├── 2020-07-19.md ├── 2020-07-20.md ├── 2020-07-21.md ├── 2020-07-22.md ├── 2020-07-23.md ├── 2020-07-24.md ├── 2020-07-25.md ├── 2020-07-26.md └── images │ ├── Clojure_logo.svg │ ├── Screenshot from 2020-06-19 00-04-04.png │ ├── athens-tweet.png │ ├── lisankie-inspiration.png │ └── open-source-lambda.png ├── project.clj ├── review.md └── target └── default ├── classes └── META-INF │ └── maven │ └── letmerepl │ └── letmerepl │ └── pom.properties └── stale └── leiningen.core.classpath.extract-native-dependencies /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 | 5 | 6 | 7 |

8 | 9 | # Learning Clojure in Public 10 | 11 | Note: My challenge is now over, you can read my thoughts on ClojureFam and my advice to new learners [here](./review.md). 12 | 13 | I am challenging myself to learn Clojure in public. Starting on July 22nd, 2020 and for the next five weeks I will be learning Clojure in public and you can hold me accountable. I am committing the code I am writing to the `./code` folder, and will also write daily posts detailing what I learned and what I did to learn it. 14 | 15 | If you would like to read why I am doing this, read the [introduction](posts/2020-06-18.md). 16 | 17 | When I announced this on [Twitter](https://twitter.com/adrien/status/1273013237076971528) I got an interesting suggestion: 18 | 19 |

20 | 21 | So here it goes: if I miss a day, you will be able ask me to send you \$5 (Venmo or PayPal preferred). If by the next morning (I often work late) I have not updated this repository with the previous' day's progress you can claim it by reaching out to [me on Twitter](https://twitter.com/adrien). There is a limit of five people per missed post. 22 | 23 | | Post | Date | Topics | 24 | | ---- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------- | 25 | | 0 | June 18th, 2020 | [Why am I doing this, why Clojure, environment setup, and the REPL](posts/2020-06-18.md) | 26 | | 1 | June 22nd, 2020 | [ClojureFam kickoff and types (Clojure from the Ground Up Chapter 1 & 2)](posts/2020-06-22.md) | 27 | | 2 | June 23rd, 2020 | [Functions (Clojure from the Ground Up Chapter 3)](posts/2020-06-23.md) | 28 | | 3 | June 24th, 2020 | [Manipulating sequences (Clojure from the Ground Up Chapter 4)](posts/2020-06-24.md) | 29 | | 4 | June 25th, 2020 | [Intro to state and concurrency (Clojure from the Ground Up Chapter 6)](posts/2020-06-25.md) | 30 | | 5 | June 26th, 2020 | [State and concurrency continued (Clojure from the Ground Up Chapter 6)](posts/2020-06-26.md) | 31 | | 6 | June 27th, 2020 | [Project setup and exploring data (Clojure from the Ground Up Chapter 7)](posts/2020-06-27.md) | 32 | | 7 | June 28th, 2020 | [Exploring data and exercises (Clojure from the Ground Up Chapter 7)](posts/2020-06-28.md) | 33 | | 8 | June 29th, 2020 | [Modeling (Clojure from the Ground Up Chapter 8)](posts/2020-06-29.md) | 34 | | 9 | June 30th, 2020 | [Modeling (continued) (Clojure from the Ground Up Chapter 8)](posts/2020-06-30.md) | 35 | | 10 | July 1st, 2020 | [Debugging Clojure (Clojure from the Ground Up Chapter 10 -- last chapter!)](posts/2020-07-01.md) | 36 | | 11 | July 2nd, 2020 | [Clojure for the Brave and True Chapter 3](posts/2020-07-02.md) | 37 | | 12 | July 3rd, 2020 | [Learn Datalog Today Chapter 0 through 6 and troubleshooting our first issue](posts/2020-07-03.md) | 38 | | 13 | July 4th, 2020 | [Learn Datalog Today Chapter 7 & 8](posts/2020-07-04.md) | 39 | | 14 | July 5th, 2020 | [Clojure for the Brave and True, first half of chapter 4](posts/2020-07-05.md) | 40 | | 15 | July 6th, 2020 | [Brave Clojure, second half of chapter 4 and its exercises: lazy seq, collection abstraction](posts/2020-07-06.md) | 41 | | 16 | July 7th, 2020 | [Functional Programming with Brave Clojure, Chapter 5 and its exercises, also opening our first PR in Athens](posts/2020-07-07.md) | 42 | | 17 | July 8th, 2020 | [Namespaces and organizing a project](posts/2020-07-08.md) | 43 | | 18 | July 9th, 2020 | [Reading and evaluating in Clojure (Brave Clojure, Chapter 7), and Reagent the minilistic React for ClojureScript](posts/2020-07-09.md) | 44 | | 19 | July 10th, 2020 | [Writing macros (Brave Clojure, Chapter 8) and Pascal Triangle problem](posts/2020-07-10.md) | 45 | | 20 | July 11th, 2020 | [Concurrency in Clojure (Brave Clojure, Chapter 9)](posts/2020-07-11.md) | 46 | | 21 | July 12th, 2020 | [Concurrency in Clojure (Brave Clojure, Chapter 9), and three 4clojure problems](posts/2020-07-12.md) | 47 | | 22 | July 13th, 2020 | [State in Clojure (Brave Clojure, Chapter 10), and two 4clojure problems](posts/2020-07-13.md) | 48 | | 23 | July 14th, 2020 | [State in Clojure (End of Brave Clojure, Chapter 10 and exercises)](posts/2020-07-14.md) | 49 | | 24 | July 15th, 2020 | [clojure.core.async (Brave Clojure, Chapter 11)](posts/2020-07-15.md) | 50 | | 25 | July 16th, 2020 | [JVM and Java interop (Brave Clojure, Chapter 12), working on issue 126](posts/2020-07-16.md) | 51 | | 26 | July 17th, 2020 | [Creating and extending Clojure abstractions (Brave Clojure, Chapter 13), learning re-frame, working on issue 126](posts/2020-07-17.md) | 52 | | 27 | July 18th, 2020 | [re-frame building blocks, working on issue 126](posts/2020-07-18.md) | 53 | | 28 | July 19th, 2020 | [Building a decide-wheel clone with re-frame](posts/2020-07-19.md) | 54 | | 29 | July 20th, 2020 | [Styling and animating the decide-wheel clone, looking into our third issue](posts/2020-07-20.md) | 55 | | 30 | July 21th, 2020 | [Trying to solve the page deletion problem, and styling the wheel some more](posts/2020-07-21.md) | 56 | | 31 | July 22th, 2020 | [Page deletion in Athens](posts/2020-07-22.md) | 57 | | 32 | July 23th, 2020 | [Implementing Filtering in Athens](posts/2020-07-23.md) | 58 | | 33 | July 24th, 2020 | [Fixing slash command scrolling in Athens, and bike-shedding hail the wheel](posts/2020-07-24.md) | 59 | | 34 | July 25th, 2020 | [Finishing the PR for slash command scrolling in Athens](posts/2020-07-25.md) | 60 | | 35 | July 26th, 2020 | [Working on filters and 4clojure's 44th problem](posts/2020-07-26.md) | 61 | -------------------------------------------------------------------------------- /code/fwpd/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .hgignore 12 | .hg/ 13 | -------------------------------------------------------------------------------- /code/fwpd/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2020-07-06 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2020-07-06 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/fwpd/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/fwpd/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /code/fwpd/README.md: -------------------------------------------------------------------------------- 1 | # fwpd 2 | 3 | FIXME: description 4 | 5 | ## Installation 6 | 7 | Download from http://example.com/FIXME. 8 | 9 | ## Usage 10 | 11 | FIXME: explanation 12 | 13 | $ java -jar fwpd-0.1.0-standalone.jar [args] 14 | 15 | ## Options 16 | 17 | FIXME: listing of options this app accepts. 18 | 19 | ## Examples 20 | 21 | ... 22 | 23 | ### Bugs 24 | 25 | ... 26 | 27 | ### Any Other Sections 28 | ### That You Think 29 | ### Might be Useful 30 | 31 | ## License 32 | 33 | Copyright © 2020 FIXME 34 | 35 | This program and the accompanying materials are made available under the 36 | terms of the Eclipse Public License 2.0 which is available at 37 | http://www.eclipse.org/legal/epl-2.0. 38 | 39 | This Source Code may also be made available under the following Secondary 40 | Licenses when the conditions for such availability set forth in the Eclipse 41 | Public License, v. 2.0 are satisfied: GNU General Public License as published by 42 | the Free Software Foundation, either version 2 of the License, or (at your 43 | option) any later version, with the GNU Classpath Exception which is available 44 | at https://www.gnu.org/software/classpath/license.html. 45 | -------------------------------------------------------------------------------- /code/fwpd/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to fwpd 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/fwpd/project.clj: -------------------------------------------------------------------------------- 1 | (defproject fwpd "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 5 | :url "https://www.eclipse.org/legal/epl-2.0/"} 6 | :dependencies [[org.clojure/clojure "1.10.1"]] 7 | :main ^:skip-aot fwpd.core 8 | :target-path "target/%s" 9 | :profiles {:uberjar {:aot :all}}) 10 | -------------------------------------------------------------------------------- /code/fwpd/src/fwpd/core.clj: -------------------------------------------------------------------------------- 1 | (ns fwpd.core) 2 | (def filename "suspects.csv") 3 | 4 | (def vamp-keys [:name :glitter-index]) 5 | 6 | (defn str->int 7 | [str] 8 | (Integer. str)) 9 | 10 | (def conversions {:name identity 11 | :glitter-index str->int}) 12 | 13 | (defn convert 14 | [vamp-key value] 15 | ((get conversions vamp-key) value)) 16 | 17 | (defn parse 18 | "Convert a CSV into rows of columns" 19 | [string] 20 | (map #(clojure.string/split % #",") 21 | (clojure.string/split string #"\n"))) 22 | 23 | (defn mapify 24 | "Return a seq of maps like {:name \"Edward Cullen\" :glitter-index 10}" 25 | [rows] 26 | (map (fn [unmapped-row] 27 | (reduce (fn [row-map [vamp-key value]] 28 | (assoc row-map vamp-key (convert vamp-key value))) 29 | {} 30 | (map vector vamp-keys unmapped-row))) 31 | rows)) 32 | 33 | (defn glitter-filter 34 | [minimum-glitter records] 35 | (filter #(>= (:glitter-index %) minimum-glitter) records)) 36 | 37 | (defn extract-names 38 | [records] 39 | (map #(:name %) records)) 40 | 41 | (defn append 42 | [records new-suspect] 43 | (conj records new-suspect)) 44 | 45 | (defn validate 46 | [keyword-to-validating-function record] 47 | (apply = true (map (fn [key] ((key keyword-to-validating-function) (key record))) (keys keyword-to-validating-function)))) 48 | 49 | (defn convert-to-csv 50 | [records] 51 | (clojure.string/join "\n" (map (fn [record] (clojure.string/join "," (map str (vals record)))) records))) -------------------------------------------------------------------------------- /code/fwpd/suspects.csv: -------------------------------------------------------------------------------- 1 | Edward Cullen,10 2 | Bella Swan,0 3 | Charlie Swan,0 4 | Jacob Black,3 5 | Carlisle Cullen,6 -------------------------------------------------------------------------------- /code/fwpd/test/fwpd/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns fwpd.core-test 2 | (:require [clojure.test :refer :all] 3 | [fwpd.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/pegthing/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .hgignore 12 | .hg/ 13 | -------------------------------------------------------------------------------- /code/pegthing/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2020-07-07 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2020-07-07 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/pegthing/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/pegthing/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /code/pegthing/README.md: -------------------------------------------------------------------------------- 1 | # pegthing 2 | 3 | FIXME: description 4 | 5 | ## Installation 6 | 7 | Download from http://example.com/FIXME. 8 | 9 | ## Usage 10 | 11 | FIXME: explanation 12 | 13 | $ java -jar pegthing-0.1.0-standalone.jar [args] 14 | 15 | ## Options 16 | 17 | FIXME: listing of options this app accepts. 18 | 19 | ## Examples 20 | 21 | ... 22 | 23 | ### Bugs 24 | 25 | ... 26 | 27 | ### Any Other Sections 28 | ### That You Think 29 | ### Might be Useful 30 | 31 | ## License 32 | 33 | Copyright © 2020 FIXME 34 | 35 | This program and the accompanying materials are made available under the 36 | terms of the Eclipse Public License 2.0 which is available at 37 | http://www.eclipse.org/legal/epl-2.0. 38 | 39 | This Source Code may also be made available under the following Secondary 40 | Licenses when the conditions for such availability set forth in the Eclipse 41 | Public License, v. 2.0 are satisfied: GNU General Public License as published by 42 | the Free Software Foundation, either version 2 of the License, or (at your 43 | option) any later version, with the GNU Classpath Exception which is available 44 | at https://www.gnu.org/software/classpath/license.html. 45 | -------------------------------------------------------------------------------- /code/pegthing/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to pegthing 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/pegthing/project.clj: -------------------------------------------------------------------------------- 1 | (defproject pegthing "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 5 | :url "https://www.eclipse.org/legal/epl-2.0/"} 6 | :dependencies [[org.clojure/clojure "1.10.1"]] 7 | :main ^:skip-aot pegthing.core 8 | :target-path "target/%s" 9 | :profiles {:uberjar {:aot :all}}) 10 | -------------------------------------------------------------------------------- /code/pegthing/src/pegthing/core.clj: -------------------------------------------------------------------------------- 1 | (ns pegthing.core 2 | (require [clojure.set :as set]) 3 | (:gen-class)) 4 | 5 | (declare successful-move prompt-move game-over prompt-rows) 6 | 7 | ;; Creating the board 8 | 9 | ; A board looks like this 10 | ; {1 {:pegged true, :connections {6 3, 4 2}} 11 | ; 2 {:pegged true, :connections {9 5, 7 4}} 12 | ; 3 {:pegged true, :connections {10 6, 8 5}} 13 | ; 4 {:pegged true, :connections {13 8, 11 7, 6 5, 1 2}} 14 | ; 5 {:pegged true, :connections {14 9, 12 8}} 15 | ; 6 {:pegged true, :connections {15 10, 13 9, 4 5, 1 3}} 16 | ; 7 {:pegged true, :connections {9 8, 2 4}} 17 | ; 8 {:pegged true, :connections {10 9, 3 5}} 18 | ; 9 {:pegged true, :connections {7 8, 2 5}} 19 | ; 10 {:pegged true, :connections {8 9, 3 6}} 20 | ; 11 {:pegged true, :connections {13 12, 4 7}} 21 | ; 12 {:pegged true, :connections {14 13, 5 8}} 22 | ; 13 {:pegged true, :connections {15 14, 11 12, 6 9, 4 8}} 23 | ; 14 {:pegged true, :connections {12 13, 5 9}} 24 | ; 15 {:pegged true, :connections {13 14, 6 10}} 25 | ; :rows 5} 26 | 27 | (defn tri* 28 | "Generates lazy sequence of triangular numbers" 29 | ([] (tri* 0 1)) 30 | ([sum n] 31 | (let [new-sum (+ sum n)] 32 | (cons new-sum (lazy-seq (tri* new-sum (inc n))))))) 33 | 34 | (def tri (tri*)) 35 | 36 | (defn triangular? 37 | "Is the number triangular? e.g. 1, 3, 6, 10, 15, etc" 38 | [n] 39 | (= n (last (take-while #(>= n %) tri)))) 40 | 41 | (defn row-tri 42 | "The triangular number at the end of row n" 43 | [n] 44 | (last (take n tri))) 45 | 46 | (defn row-num 47 | "Returns row number the position belongs to: pos 1 in row 1, 48 | positions 2 and 3 in row 2, etc" 49 | [pos] 50 | (inc (count (take-while #(> pos %) tri)))) 51 | 52 | (defn connect 53 | "Form a mutual connection between two positions" 54 | [board max-pos pos neighbor destination] 55 | (if (<= destination max-pos) 56 | (reduce (fn [new-board [p1 p2]] 57 | (assoc-in new-board [p1 :connections p2] neighbor)) 58 | board 59 | [[pos destination] [destination pos]]) 60 | board)) 61 | 62 | (defn connect-right 63 | [board max-pos pos] 64 | (let [neighbor (inc pos) 65 | destination (inc neighbor)] 66 | (if-not (or (triangular? neighbor) (triangular? pos)) 67 | (connect board max-pos pos neighbor destination) 68 | board))) 69 | 70 | (defn connect-down-left 71 | [board max-pos pos] 72 | (let [row (row-num pos) 73 | neighbor (+ row pos) 74 | destination (+ 1 row neighbor)] 75 | (connect board max-pos pos neighbor destination))) 76 | 77 | (defn connect-down-right 78 | [board max-pos pos] 79 | (let [row (row-num pos) 80 | neighbor (+ 1 row pos) 81 | destination (+ 2 row neighbor)] 82 | (connect board max-pos pos neighbor destination))) 83 | 84 | (defn add-pos 85 | "Pegs the position and performs connections" 86 | [board max-pos pos] 87 | (let [pegged-board (assoc-in board [pos :pegged] true)] 88 | (reduce (fn [new-board connection-creation-fn] 89 | (connection-creation-fn new-board max-pos pos)) 90 | pegged-board 91 | [connect-right connect-down-left connect-down-right]))) 92 | 93 | (defn new-board 94 | "Creates a new board with the given number of rows" 95 | [rows] 96 | (let [initial-board {:rows rows} 97 | max-pos (row-tri rows)] 98 | (reduce (fn [board pos] (add-pos board max-pos pos)) 99 | initial-board 100 | (range 1 (inc max-pos))))) 101 | 102 | 103 | ;; Moving pegs 104 | 105 | (defn pegged? 106 | "Does the position have a peg in it?" 107 | [board pos] 108 | (get-in board [pos :pegged])) 109 | 110 | (defn remove-peg 111 | "Take the peg at given position out of the board" 112 | [board pos] 113 | (assoc-in board [pos :pegged] false)) 114 | 115 | (defn place-peg 116 | "Put a peg in the board at given position" 117 | [board pos] 118 | (assoc-in board [pos :pegged] true)) 119 | 120 | (defn move-peg 121 | "Take peg out of p1 and place it in p2" 122 | [board p1 p2] 123 | (place-peg (remove-peg board p1) p2)) 124 | 125 | (defn valid-moves 126 | "Return a map of all valid moves for pos, where the key is the 127 | destination and the value is the jumped position" 128 | [board pos] 129 | (into {} 130 | (filter (fn [[destination jumped]] 131 | (and (not (pegged? board destination)) 132 | (pegged? board jumped))) 133 | (get-in board [pos :connections])))) 134 | 135 | (defn valid-move? 136 | "Return jumped position if the move from p1 to p2 is valid, nil 137 | otherwise" 138 | [board p1 p2] 139 | (get (valid-moves board p1) p2)) 140 | 141 | (defn make-move 142 | "Move peg from p1 to p2, removing jumped peg" 143 | [board p1 p2] 144 | (if-let [jumped (valid-move? board p1 p2)] 145 | (move-peg (remove-peg board jumped) p1 p2))) 146 | 147 | (defn can-move? 148 | "Do any of the pegged positions have valid moves?" 149 | [board] 150 | (some (comp not-empty (partial valid-moves board)) 151 | (map first (filter #(get (second %) :pegged) board)))) 152 | 153 | 154 | ;; Rendering and printing the board 155 | 156 | (def alpha-start 97) 157 | (def alpha-end 123) 158 | (def letters (map (comp str char) (range alpha-start alpha-end))) 159 | (def pos-chars 3) 160 | 161 | (def ansi-styles 162 | {:red "[31m" 163 | :green "[32m" 164 | :blue "[34m" 165 | :reset "[0m"}) 166 | 167 | (defn ansi 168 | "Produce a string which will apply an ansi style" 169 | [style] 170 | (str \u001b (style ansi-styles))) 171 | 172 | (defn colorize 173 | "Apply ansi color to text" 174 | [text color] 175 | (str (ansi color) text (ansi :reset))) 176 | 177 | (defn render-pos 178 | [board pos] 179 | (str (nth letters (dec pos)) 180 | (if (get-in board [pos :pegged]) 181 | (colorize "0" :blue) 182 | (colorize "-" :red)))) 183 | 184 | (defn row-positions 185 | "Return all positions in the given row" 186 | [row-num] 187 | (range (inc (or (row-tri (dec row-num)) 0)) 188 | (inc (row-tri row-num)))) 189 | 190 | (defn row-padding 191 | "String of spaces to add to the beginning of a row to center it" 192 | [row-num rows] 193 | (let [pad-length (/ (* (- rows row-num) pos-chars) 2)] 194 | (apply str (take pad-length (repeat " "))))) 195 | 196 | (defn render-row 197 | [board row-num] 198 | (str (row-padding row-num (:rows board)) 199 | (clojure.string/join " " (map (partial render-pos board) 200 | (row-positions row-num))))) 201 | 202 | (defn print-board 203 | [board] 204 | (doseq [row-num (range 1 (inc (:rows board)))] 205 | (println (render-row board row-num)))) 206 | 207 | ;; Player interaction 208 | 209 | (defn letter->pos 210 | "Converts a letter string to the corresponding position number" 211 | [letter] 212 | (inc (- (int (first letter)) alpha-start))) 213 | 214 | (defn get-input 215 | "Waits for user to enter text and hit enter, then cleans the input" 216 | ([] (get-input nil)) 217 | ([default] 218 | (let [input (clojure.string/trim (read-line))] 219 | (if (empty? input) 220 | default 221 | (clojure.string/lower-case input))))) 222 | 223 | (defn characters-as-strings 224 | "Given a string, return a collection consisting of each individual 225 | character" 226 | [string] 227 | (re-seq #"[a-zA-Z]" string)) 228 | 229 | (defn user-entered-invalid-move 230 | "Handles the next step after a user has entered an invalid move" 231 | [board] 232 | (println "\n!!! That was an invalid move :(\n") 233 | (prompt-move board)) 234 | 235 | (defn user-entered-valid-move 236 | "Handles the next step after a user has entered a valid move" 237 | [board] 238 | (if (can-move? board) 239 | (prompt-move board) 240 | (game-over board))) 241 | 242 | (defn prompt-move 243 | [board] 244 | (println "\nHere's your board:") 245 | (print-board board) 246 | (println "Move from where to where? Enter two letters:") 247 | (let [input (map letter->pos (characters-as-strings (get-input)))] 248 | (if-let [new-board (make-move board (first input) (second input))] 249 | (user-entered-valid-move new-board) 250 | (user-entered-invalid-move board)))) 251 | 252 | (defn game-over 253 | "Announce the game is over and prompt to play again" 254 | [board] 255 | (let [remaining-pegs (count (filter :pegged (vals board)))] 256 | (println "Game over! You had" remaining-pegs "pegs left:") 257 | (print-board board) 258 | (println "Play again? y/n [y]") 259 | (let [input (get-input "y")] 260 | (if (= "y" input) 261 | (prompt-rows) 262 | (do 263 | (println "Bye!") 264 | (System/exit 0)))))) 265 | 266 | (defn prompt-empty-peg 267 | [board] 268 | (println "Here's your board:") 269 | (print-board board) 270 | (println "Remove which peg? [e]") 271 | (prompt-move (remove-peg board (letter->pos (get-input "e"))))) 272 | 273 | (defn prompt-rows 274 | [] 275 | (println "How many rows? [5]") 276 | (let [rows (Integer. (get-input 5)) 277 | board (new-board rows)] 278 | (prompt-empty-peg board))) -------------------------------------------------------------------------------- /code/pegthing/test/pegthing/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns pegthing.core-test 2 | (:require [clojure.test :refer :all] 3 | [pegthing.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/playsync/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .hgignore 12 | .hg/ 13 | -------------------------------------------------------------------------------- /code/playsync/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2020-07-15 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2020-07-15 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/playsync/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/playsync/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /code/playsync/README.md: -------------------------------------------------------------------------------- 1 | # playsync 2 | 3 | FIXME: description 4 | 5 | ## Installation 6 | 7 | Download from http://example.com/FIXME. 8 | 9 | ## Usage 10 | 11 | FIXME: explanation 12 | 13 | $ java -jar playsync-0.1.0-standalone.jar [args] 14 | 15 | ## Options 16 | 17 | FIXME: listing of options this app accepts. 18 | 19 | ## Examples 20 | 21 | ... 22 | 23 | ### Bugs 24 | 25 | ... 26 | 27 | ### Any Other Sections 28 | ### That You Think 29 | ### Might be Useful 30 | 31 | ## License 32 | 33 | Copyright © 2020 FIXME 34 | 35 | This program and the accompanying materials are made available under the 36 | terms of the Eclipse Public License 2.0 which is available at 37 | http://www.eclipse.org/legal/epl-2.0. 38 | 39 | This Source Code may also be made available under the following Secondary 40 | Licenses when the conditions for such availability set forth in the Eclipse 41 | Public License, v. 2.0 are satisfied: GNU General Public License as published by 42 | the Free Software Foundation, either version 2 of the License, or (at your 43 | option) any later version, with the GNU Classpath Exception which is available 44 | at https://www.gnu.org/software/classpath/license.html. 45 | -------------------------------------------------------------------------------- /code/playsync/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to playsync 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/playsync/project.clj: -------------------------------------------------------------------------------- 1 | (defproject playsync "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 5 | :url "https://www.eclipse.org/legal/epl-2.0/"} 6 | :dependencies [[org.clojure/clojure "1.10.1"] [org.clojure/core.async "0.1.346.0-17112a-alpha"]] 7 | :main ^:skip-aot playsync.core 8 | :target-path "target/%s" 9 | :profiles {:uberjar {:aot :all}}) 10 | -------------------------------------------------------------------------------- /code/playsync/src/playsync/core.clj: -------------------------------------------------------------------------------- 1 | (ns playsync.core 2 | (:require [clojure.core.async 3 | :as a 4 | :refer [>! !! !! echo-chan "stuff")) -------------------------------------------------------------------------------- /code/playsync/test/playsync/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns playsync.core-test 2 | (:require [clojure.test :refer :all] 3 | [playsync.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/scratch/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .hgignore 12 | .hg/ 13 | -------------------------------------------------------------------------------- /code/scratch/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 4 | 5 | ## [Unreleased] 6 | 7 | ### Changed 8 | 9 | - Add a new arity to `make-widget-async` to provide a different widget shape. 10 | 11 | ## [0.1.1] - 2020-06-27 12 | 13 | ### Changed 14 | 15 | - Documentation on how to make the widgets. 16 | 17 | ### Removed 18 | 19 | - `make-widget-sync` - we're all async, all the time. 20 | 21 | ### Fixed 22 | 23 | - Fixed widget maker to keep working when daylight savings switches over. 24 | 25 | ## 0.1.0 - 2020-06-27 26 | 27 | ### Added 28 | 29 | - Files from the new template. 30 | - Widget maker public API - `make-widget-sync`. 31 | 32 | [unreleased]: https://github.com/your-name/scratch 33 | 34 | /compare/0.1.1...HEAD 35 | [0.1.1]: https://github.com/your-name/scratch 36 | /compare/0.1.0...0.1.1 37 | -------------------------------------------------------------------------------- /code/scratch/README.md: -------------------------------------------------------------------------------- 1 | # scratch 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2020 FIXME 12 | 13 | This program and the accompanying materials are made available under the 14 | terms of the Eclipse Public License 2.0 which is available at 15 | http://www.eclipse.org/legal/epl-2.0. 16 | 17 | This Source Code may also be made available under the following Secondary 18 | Licenses when the conditions for such availability set forth in the Eclipse 19 | Public License, v. 2.0 are satisfied: GNU General Public License as published by 20 | the Free Software Foundation, either version 2 of the License, or (at your 21 | option) any later version, with the GNU Classpath Exception which is available 22 | at https://www.gnu.org/software/classpath/license.html. 23 | -------------------------------------------------------------------------------- /code/scratch/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to scratch 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/scratch/project.clj: -------------------------------------------------------------------------------- 1 | (defproject scratch "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.10.0"] 7 | [cheshire "5.3.1"]]) 8 | -------------------------------------------------------------------------------- /code/scratch/src/scratch/core.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.core) 2 | 3 | (defn pow 4 | "Raises base to the given power. For instance, (pow 3 2) returns three squared, or nine." 5 | [base power] 6 | (apply * (repeat power base))) 7 | -------------------------------------------------------------------------------- /code/scratch/src/scratch/crime.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.crime 2 | (:require [cheshire.core :as json])) 3 | 4 | (defn load-json 5 | "Given a filename, reads a JSON file and returns it, parsed, with keywords." 6 | [file] 7 | (json/parse-string (slurp file) true)) 8 | 9 | (def fips 10 | "A map of FIPS codes to their county names." 11 | (->> "fips.json" 12 | load-json 13 | :table 14 | :rows 15 | (into {}))) 16 | 17 | (defn fips-code 18 | "Given a county (a map with :fips_state_code and :fips_county_code keys), returns the five-digit FIPS code for the 19 | county, as a string." 20 | [county] 21 | (str (:fips_state_code county) (:fips_county_code county))) 22 | 23 | (defn most-duis 24 | "Given a JSON filename of UCR crime data for a particular year, finds the counties with the most DUIs." 25 | [file] 26 | (->> file 27 | load-json 28 | (sort-by :driving_under_influence) 29 | (take-last 10) 30 | (map (fn [county] 31 | [(fips (fips-code county)) 32 | [:duis (:driving_under_influence county) 33 | :population (:county_population county) 34 | :report-count (:grand_total county) 35 | :prevalence (double (/ (:driving_under_influence county) (:county_population county)))]])) 36 | (into {}))) 37 | 38 | 39 | (defn prevalence-of-duis 40 | "Prevalence of DUIs." 41 | [file] 42 | (->> file 43 | load-json 44 | (map (fn [county] 45 | {:county (fips (fips-code county)) 46 | :duis (:driving_under_influence county) 47 | :population (:county_population county) 48 | :prevalence (double (if 49 | (pos? (:county_population county)) 50 | (/ (:driving_under_influence county) (:county_population county)) 51 | 0))})) 52 | (sort-by :prevalence) 53 | (take-last 10))) 54 | 55 | (defn most-prevalent 56 | "Given a JSON filename of UCR crime data for a particular year, and a crime, find the counties with the highest prevalence of this crime." 57 | [file crime] 58 | (->> file 59 | load-json 60 | (map (fn [county] 61 | {:county (fips (fips-code county)) 62 | :occurrence (crime county) 63 | :population (:county_population county) 64 | :prevalence (double (if 65 | (pos? 66 | (:county_population county)) 67 | (/ 68 | (crime county) 69 | (:county_population county)) 70 | 0))})) 71 | (sort-by :prevalence) 72 | (take-last 10))) -------------------------------------------------------------------------------- /code/scratch/test/scratch/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.core-test 2 | (:require [clojure.test :refer :all] 3 | [scratch.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "Numbers are equal to themselves, right?" 7 | (is (= 0 0)))) 8 | 9 | (deftest pow-test 10 | (testing "unity" 11 | (is (= 1 (pow 1 1)))) 12 | (testing "square integers" 13 | (is (= 9 (pow 3 2)))) 14 | (testing "0^0" 15 | (is (= 1 (pow 0 0))))) 16 | -------------------------------------------------------------------------------- /code/scratch/test/scratch/crime_test.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.crime-test 2 | (:require [clojure.test :refer :all] 3 | [scratch.crime :refer :all])) 4 | 5 | (deftest fips-code-test 6 | (is (= "12345" (fips-code {:fips_state_code "12" 7 | :fips_county_code "345"})))) 8 | -------------------------------------------------------------------------------- /code/scratch/test/scratch/rocket_test.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.rocket-test 2 | (:require [clojure.test :refer [deftest testing is]] 3 | [scratch.rocket :refer [cartesian->spherical spherical->cartesian atlas-v prepare trajectory crashed? crash-time apoapsis apoapsis-time centaur]])) 4 | 5 | (deftest spherical-coordinate-test 6 | 7 | (testing "spherical->cartesian" 8 | (is (= (spherical->cartesian {:r 2 9 | :phi 0 10 | :theta 0}) 11 | {:x 0.0 :y 0.0 :z 2.0}))) 12 | 13 | (testing "roundtrip" 14 | (let [pos {:x 1.0 :y 2.0 :z 3.0}] 15 | (is (= pos (-> pos cartesian->spherical spherical->cartesian)))))) 16 | 17 | (deftest makes-orbit 18 | (let [trajectory (->> (atlas-v (centaur)) 19 | prepare 20 | (trajectory 1))] 21 | 22 | (when (crashed? trajectory) 23 | (println "Crashed at" (crash-time trajectory) "seconds") 24 | (println "Maximum altitude" (apoapsis trajectory) 25 | "meters at" (apoapsis-time trajectory) "seconds")) 26 | 27 | ; Assert that the rocket eventually made it to orbit. 28 | (is (not (crashed? trajectory))))) 29 | -------------------------------------------------------------------------------- /code/the-divine-cheese-code/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .hgignore 12 | .hg/ 13 | -------------------------------------------------------------------------------- /code/the-divine-cheese-code/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2020-07-08 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2020-07-08 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/the-divine-cheese-code/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/the-divine-cheese-code/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /code/the-divine-cheese-code/README.md: -------------------------------------------------------------------------------- 1 | # the-divine-cheese-code 2 | 3 | FIXME: description 4 | 5 | ## Installation 6 | 7 | Download from http://example.com/FIXME. 8 | 9 | ## Usage 10 | 11 | FIXME: explanation 12 | 13 | $ java -jar the-divine-cheese-code-0.1.0-standalone.jar [args] 14 | 15 | ## Options 16 | 17 | FIXME: listing of options this app accepts. 18 | 19 | ## Examples 20 | 21 | ... 22 | 23 | ### Bugs 24 | 25 | ... 26 | 27 | ### Any Other Sections 28 | ### That You Think 29 | ### Might be Useful 30 | 31 | ## License 32 | 33 | Copyright © 2020 FIXME 34 | 35 | This program and the accompanying materials are made available under the 36 | terms of the Eclipse Public License 2.0 which is available at 37 | http://www.eclipse.org/legal/epl-2.0. 38 | 39 | This Source Code may also be made available under the following Secondary 40 | Licenses when the conditions for such availability set forth in the Eclipse 41 | Public License, v. 2.0 are satisfied: GNU General Public License as published by 42 | the Free Software Foundation, either version 2 of the License, or (at your 43 | option) any later version, with the GNU Classpath Exception which is available 44 | at https://www.gnu.org/software/classpath/license.html. 45 | -------------------------------------------------------------------------------- /code/the-divine-cheese-code/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to the-divine-cheese-code 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/the-divine-cheese-code/project.clj: -------------------------------------------------------------------------------- 1 | (defproject the-divine-cheese-code "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 5 | :url "https://www.eclipse.org/legal/epl-2.0/"} 6 | :dependencies [[org.clojure/clojure "1.10.1"]] 7 | :main ^:skip-aot the-divine-cheese-code.core 8 | :target-path "target/%s" 9 | :profiles {:uberjar {:aot :all}}) 10 | -------------------------------------------------------------------------------- /code/the-divine-cheese-code/src/the_divine_cheese_code/core.clj: -------------------------------------------------------------------------------- 1 | 2 | (ns the-divine-cheese-code.core 3 | (:require [clojure.java.browse :as browse] 4 | [the-divine-cheese-code.visualization.svg :refer [xml]]) 5 | (:gen-class)) 6 | 7 | (def heists [{:location "Cologne, Germany" 8 | :cheese-name "Archbishop Hildebold's Cheese Pretzel" 9 | :lat 50.95 10 | :lng 6.97} 11 | {:location "Zurich, Switzerland" 12 | :cheese-name "The Standard Emmental" 13 | :lat 47.37 14 | :lng 8.55} 15 | {:location "Marseille, France" 16 | :cheese-name "Le Fromage de Cosquer" 17 | :lat 43.30 18 | :lng 5.37} 19 | {:location "Zurich, Switzerland" 20 | :cheese-name "The Lesser Emmental" 21 | :lat 47.37 22 | :lng 8.55} 23 | {:location "Vatican City" 24 | :cheese-name "The Cheese of Turin" 25 | :lat 41.90 26 | :lng 12.45}]) 27 | 28 | (defn url 29 | [filename] 30 | (str "file:///" 31 | (System/getProperty "user.dir") 32 | "/" 33 | filename)) 34 | 35 | (defn template 36 | [contents] 37 | (str "" 38 | contents)) 39 | 40 | (defn -main 41 | [& args] 42 | (let [filename "map.html"] 43 | (->> heists 44 | (xml 50 100) 45 | template 46 | (spit filename)) 47 | (browse/browse-url (url filename)))) 48 | -------------------------------------------------------------------------------- /code/the-divine-cheese-code/src/the_divine_cheese_code/visualization/svg.clj: -------------------------------------------------------------------------------- 1 | (ns the-divine-cheese-code.visualization.svg 2 | (:require [clojure.string :as s]) 3 | (:refer-clojure :exclude [min max])) 4 | 5 | (defn comparator-over-maps 6 | [comparison-fn ks] 7 | (fn [maps] 8 | (zipmap ks 9 | (map (fn [k] (apply comparison-fn (map k maps))) 10 | ks)))) 11 | 12 | (def min (comparator-over-maps clojure.core/min [:lat :lng])) 13 | (def max (comparator-over-maps clojure.core/max [:lat :lng])) 14 | 15 | (defn translate-to-00 16 | [locations] 17 | (let [mincoords (min locations)] 18 | (map #(merge-with - % mincoords) locations))) 19 | 20 | (defn scale 21 | [width height locations] 22 | (let [maxcoords (max locations) 23 | ratio {:lat (/ height (:lat maxcoords)) 24 | :lng (/ width (:lng maxcoords))}] 25 | (map #(merge-with * % ratio) locations))) 26 | 27 | (defn latlng->point 28 | "Convert lat/lng map to comma-separated string" 29 | [latlng] 30 | (str (:lat latlng) "," (:lng latlng))) 31 | 32 | (defn points 33 | "Given a seq of lat/lng maps, return string of points joined by space" 34 | [locations] 35 | (s/join " " (map latlng->point locations))) 36 | 37 | (defn line 38 | [points] 39 | (str "")) 40 | 41 | (defn transform 42 | "Just chains other functions" 43 | [width height locations] 44 | (->> locations 45 | translate-to-00 46 | (scale width height))) 47 | 48 | (defn xml 49 | "svg 'template', which also flips the coordinate system" 50 | [width height locations] 51 | (str "" 52 | ;; These two tags change the coordinate system so that 53 | ;; 0,0 is in the lower-left corner, instead of SVG's default 54 | ;; upper-left corner 55 | "" 56 | "" 57 | (-> (transform width height locations) 58 | points 59 | line) 60 | "" 61 | "")) -------------------------------------------------------------------------------- /code/the-divine-cheese-code/test/the_divine_cheese_code/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns the-divine-cheese-code.core-test 2 | (:require [clojure.test :refer :all] 3 | [the-divine-cheese-code.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /posts/2020-06-18.md: -------------------------------------------------------------------------------- 1 | # Why Am I Doing This? 2 | 3 | Learning is fun, learning in public is harder, so then why am I doing it? 4 | 5 | ## 📒 I want to be held accountable 6 | 7 | I am doing this for myself (see above, the primary reason is to learn, selfishly) and do not expect any sort of audience. Now if I artificially get an audience by promising easy money, that is a win! If I can't get out of this (not that I will), or if it hurts to skip a day I have a better chance for it to stick. 8 | 9 |

10 | Now there's a whole lot of people on Twitter that will hold me accountable! 11 | 12 | ## 🤝 I want to meet new people 13 | 14 | Since announcing my intention to learn Clojure in public on Twitter, I have already received feedback from strangers, found new (relevant) accounts to follow and been followed by new people. 15 | I am not trying to draw early conclusions, but any interaction around Clojure is already a plus and will probably me more than if I had stayed dark (i.e. learning in my corner). 16 | 17 | ## 🦾 I want to create a habit 18 | 19 | When I first seriously learned how to write code, I committed to keeping my GitHub streak going. It was after I graduated from bootcamp and I was a TA it was easy to fall off the bandwagon so instead I forced myself to commit something everyday. It didn't matter how small. It went on for a few months until I got a job and I started committing somewhere else. 20 | I hope to recreate this here. 21 | 22 | ## ⭕ I want to be corrected 23 | 24 | If I don't put anything out there, how will I know when I am wrong? Hopefully I can recreate the feedback loop you can experience in school, but using people of the internet! 25 | 26 | ## 👨‍🎤 (if possible) I want to inspire 27 | 28 | I was myself inspired by watching others learn in public. If I can convince one person to do it as well (especially if it turns out to be successful) then it's another win. 29 | 30 | ## ✍️ I want to get in the habit of writing 31 | 32 | I rarely write. And when I do, it's as little as possible. In the description of my pull request I am almost laconic. This is probably an easy way to fight this, not dissimilar to people joining Toastmasters to change their attitude towards public speaking. 33 | 34 | # Why Clojure? 35 | 36 | ## 📖 I want to contribute to open source 37 | 38 | Whenever I learned something and it stuck was when I was learning to build something. It has been for myself or someone else. I learned bash scripting when I wanted to automate my media server, I learned JavaScript because I needed to go from websites to applications, etc. I want to contribute to Athens because it's a project I would use so that looks like a good match as well as a learning opportunity. 39 | 40 | ## 💻 I've always been interested in Lisps 41 | 42 | it started with emacs and I wanted to customize it with emacs-lisp then a curiosity in its own right. I read [Concrete Abstractions – An Introduction to Computer Science Using Scheme](https://gustavus.edu/mcs/max/concrete-abstractions.html) and I've considered tackling Clojure as a lisp that is actually used in production. 43 | 44 | ## Other reasons 45 | 46 | - I want to learn something new. 47 | - I heard it fits very nicely with React, I'm here to see for myself. 48 | - I am interested in functional programming. 49 | - And all the other advantages: simplicity, macros, the REPL, ClojureScript 50 | 51 | # What I Did Today 52 | 53 | ## Code editor setup 54 | 55 | I already ran the development version of Athens so I do not need to setup the JVM, Clojure or lein. What I need to get ready for though, is writing Clojure and I need to pick a code editor. 56 | At this point I do not know what the ideal setup will be for me so I have setup two different ones and I will later which one is appropriate. 57 | 58 | ### Doom Emacs with Cider 59 | 60 | Very ironically or not, I stopped using emacs three weeks ago and moved my notes from org-mode to Roam Research. (~~You'll notice that these notes are in org-mode~~ I already gave up, it's much easier to copy paste from Roam to Markdown). So today I fired good old emacs again and set it up for Clojure. Most recently I was a Doom Emacs user, so I promptly uncommented the Clojure line in my init.el and [read the documentation](https://github.com/hlissner/doom-emacs/tree/develop/modules/lang/clojure) I also installed Cider but haven't gotten autocompletion to work just yet. 61 | 62 | ### VS Code with Calva 63 | 64 | I use VS Code at work, for its effortless integrations with the JavaScript and TypeScript tool chains so I decided to give it a try. The setup was effortless and there was no configuration to get the REPL once I was in Athens repository. However, the extension warns that it conflicts with some vim keybindings in vscodevim and you cannot reset these. 65 | Ultimately what will help me decide is how integrated these two solutions are with other tooling (Does a Prettier solution for Clojure exist out there? That would make my day). It does feel like VS Code is much simpler to setup here, and it's already what I use for JavaScript so I may stick with it. 66 | 67 | ## The REPL 68 | 69 | Following the on-boarding for [New Clojurians onboarding document](https://www.notion.so/Onboarding-for-New-Clojurians-b34b38f30902448cae68afffa02425c1) I read [this article](https://vvvvalvalval.github.io/posts/what-makes-a-good-repl.html) about the Clojure REPL. In the video, the author uses it to evaluate code that is in his file currently. This is great, it does remove the friction you have when trying something your browser's console: no copy/paste and it's the exact same execution environment. In some ways it reminds me of literate programming as you can execute specific part of the code, in isolation. It's a lot less monolithic than most things I have seen so far. The cURL example from the author particularly resonated with me. Also the fact that the REPL saves your result, so you can work on processing the JSON response of an API over and over again without having to re-fetch it. 70 | 71 | ### What are the advantages of the REPL 72 | 73 | - A tight feedback loop 74 | 75 | - Potentially less tests, or better tests especially if you have been writing working code in the REPL, you can reuse it in your tests 76 | 77 | I probably won't want this in JavaScript or TypeScript (I doubt that it's that tightly integrated) but I am looking forward working with it in Clojure. 78 | I will most likely go back to this article after I have some Clojure experience under my belt. 79 | -------------------------------------------------------------------------------- /posts/2020-06-22.md: -------------------------------------------------------------------------------- 1 | This is my first post in my project to learn Clojure in public. It has been more reading than writing code at this point and I mostly covered types so far. There are more types in Clojure than the ones I used in Scheme, where even lists were made out of pairs (and pairs (and pairs (and pairs))). 2 | 3 | # ClojureFam kickoff 4 | 5 | I'm learning Clojure in public as I mentioned in my [initial post](./2020-06-18.md) with Athen's ClojureFam. I am part of the 4th cohort and our team is called Seneca. 6 | Each cohort is made up of very few people (we are 6) that focus on learning Clojure together for 5 weeks. How much interaction we are going to have remain to be seen and it feels pretty much up to us to decide (especially since we are a mentor-less cohort). 7 | So today marks the beginning of my Clojure journey, with ClojureFam. 8 | 9 | ## My Goals 10 | 11 | - Completing 100 problems on 4clojure (this number is TBD) 12 | - Working through Clojure from the Ground Up, completing exercises 13 | - Working through Brave Clojure, completing exercises 14 | - Contributing to Athens' codebase 15 | 16 | One of the other learners in my cohort also decided to learn clojure in public so if you read this, be sure [to hold him accountable as well](https://twitter.com/itsrainingmani/status/1275145477273661441)! I guess I can check the ["inspire" box](https://github.com/alaq/learning-clojure-in-public/blob/master/posts/2020-06-18.md#-if-possible-i-want-to-inspire)! 17 | 18 | # Clojure from the Ground Up -- [Chapter 1](http://aphyr.com/posts/301-clojure-from-the-ground-up-first-principles) and [2](https://aphyr.com/posts/302-clojure-from-the-ground-up-basic-types) 19 | 20 | The first thing you learn in any language is the **types** of values. Types are values that work together. The first chapter covered some syntax and lists but it was very similar to Scheme so I didn't feel it was worthy of taking in depth notes as it will be covered in more details later. The interesting thing is the notes I already took I was able to save for later chapters. It's already writing itself! 21 | Interestingly enough I ended up taking notes cheat sheet style. Something I should probably be doing as well fairly soon is making Anki flashcards out of it. The goal of all this is indeed to retain it forever. 22 | While I learned about the types I made a [cheatsheet](../functions.md) of all the functions I encountered while doing so. Turns out I got quite a few already. 23 | 24 | ## Types 25 | 26 | ### Numerical values 27 | 28 | #### Integers 29 | 30 | Like `3`, `(type 3)` will return `java.lang.Long`. They are stored in 64 bits, one bit for the sign, the rest for the size. So the highest long will be 2^63-1. For anything beyond that we can use `bigint` , which will be displayed like this `8N`. So `(inc (bigint Long/MAX_VALUE))` will return `9223372036854775808N`. 31 | 32 | There are smaller numbers too: 33 | 34 | - Bytes `(byte 0)`, 8 bits so maximum value is `2^7-1` 35 | - Shorts `(short 0)`, 16 bits so maximum value is `2^15-1` 36 | - Integers `(int 0)`, 32 bits, so maximum value is `2^31-1` 37 | 38 | #### Fractional numbers 39 | 40 | Floating point numbers are either Doubles (64 bits and also the default) or Floats (32 bits). They are approximations and if we want to represent fractions exactly we can use ratios, like `1/3`. 41 | 42 | ### Strings 43 | 44 | They are of the type `java.lang.String`. You can turn almost anything into a string with the `str` function. TODO link to the str entry in the function cheatsheet. 45 | 46 | ### Booleans 47 | 48 | Like in JavaScript, there are falsy values (`false`, `nil`). The rest is truthy. You can find out the truthiness of a value with the `boolean` function. `0` is not considered falsy. 49 | 50 | ### Symbols 51 | 52 | Symbols refer to things, point to other values. When a program is evaluated, they are replaced by their corresponding values. 53 | Symbols can be namespaced with `/`. These names are the fully qualified names that lets us access a symbol from anywhere (as opposed to the short name). 54 | 55 | ### Keyword 56 | 57 | These are new, they were not to my knowledge in Scheme. Their usage is not very clear for now so I will probably have to come back to edit this later. They are of this form `:cat`, `(type :cat)` => `cat`. 58 | 59 | ### Lists 60 | 61 | Lists contain elements, or members. They can contain anything, including other lists. Lists are quoted with ' or constructed with `list`, to prevent being evaluated. `=` compares lists. `(= (list 1 2) (list 1 2))`. `first` returns the first element, `last` the last, and `nth` the nth element. The first element being at index 0. 62 | Lists are well suited for small collections that are read in linear order. Getting an arbitrary member can be slow, vectors are better suited for this. 63 | 64 | ### Vectors 65 | 66 | Vectors are like this `[1, 2, 3]`. They are not evaluated so no need to quote them. Use `vec` to build a vector. One thing to note is that `conj` will as **to the end** of the vector (contrary to lists). `nth` will be fast on vectors. `count` will give us the size of a vector. You can return the element at an index `i` like this `([1, 2, 3] 1)`, it will return `2`. 67 | Lists and vectors containing the same members are considered equal. 68 | 69 | ### Sets 70 | 71 | This is for unordered collections of values, and it is written `#{1, 2, 3}`. Sets cannot contain any element more than once. You can also use them as a **verb** (like vectors) and it will return the element itself. `(#{1 2 3} 3)` returns `3`. 72 | 73 | ### Maps 74 | 75 | This is a data structure that associates keys with values. A map's member alternate between keys and values. `(get {:name "maceo" :age 14} :name)` will return `maceo`. An extra argument will be the default value if the key is not found. And the same way you can use a map as a **verb**, `({"amlodipine" 12 "ibuprofen" 50} "ibuprofen")` will return `50`. The other way around works too. 76 | 77 | # Extracurricular readings 78 | 79 | ## [Conversational software development -- Oliver Calwell](https://oli.me.uk/conversational-software-development/) 80 | 81 | I read this article that was posted last week on Hacker News. While it was interesting it didn't bring more than what I got from [this other article about the REPL](https://github.com/alaq/learning-clojure-in-public/blob/master/posts/2020-06-18.md#the-repl) I covered a few days ago. 82 | -------------------------------------------------------------------------------- /posts/2020-06-23.md: -------------------------------------------------------------------------------- 1 | # Accessing members of a collection by using it as a **verb** 2 | 3 | ## 4clojure's 21st problem 4 | 5 | The question of this problem was to reimplement `nth`. `nth` retrieves the nth item of a collection. Since I know a little bit of Scheme I immediately reached for a recursive pattern. This is definitely possible for this problem but there must be a better solution that I did not think about. And if I don't force myself I will not be learning much Clojure. 6 | 7 | My answer, with a recursion `(fn nthel [l n] (if (= n 0) (first l) (nthel (rest l) (- n 1))))` 8 | 9 | ## Accessing members 10 | 11 | However it is possible to access a vector's members by using the vector as verb: 12 | 13 | ```clojure 14 | 15 | user=> ([:a :b :c] 1) ; :b 16 | 17 | ``` 18 | 19 | My new solution is much shorter: 20 | 21 | ```clojure 22 | 23 | (fn nthel [l n] ((vec l) n)) 24 | 25 | ``` 26 | 27 | It was interesting to see that most of the people who completed all of the 4clojure problems also used a recursion. 28 | 29 | # Clojure from the Ground Up -- [Chapter 3](https://aphyr.com/posts/303-clojure-from-the-ground-up-functions) 30 | 31 | ## Let bindings 32 | 33 | `let` lets you declare a symbol, within a specific expression. This looks like a local variable. 34 | 35 | - It takes a vector, and we alternate between symbol and meaning. 36 | 37 | - They will be executed in order. 38 | 39 | - Symbols won't be accessible outside of the expression. 40 | 41 | - You can also override an existing symbol. 42 | 43 | - `let`s cannot be reassigned. 44 | 45 | ### Example 46 | 47 | ```clojure 48 | (let [cats 5] 49 | (str "I have " cats " cats.")) ; will return "I have 5 cats." 50 | ``` 51 | 52 | # Vars 53 | 54 | Vars are mutable variables and use the `def` keyword to be created. It's a different type of value. It binds a symbol to a value, like this `(def cats 5)`. This is to be used carefully in Clojure. 55 | 56 | ## Functions 57 | 58 | **Functions** are like verbs, `inc` is a symbol which points to a "verb". In this case, `inc` is short for increment. You can refer to a symbol without evaluating it with a quote, so like `'inc`, which will return `inc`. It will be the case for every value and also function calls like `'(inc 1)`. I have started collecting [here](../functions.md) the functions that I encounter. You always put the function first and can add the arguments afterwards. A function is like a `let`, but unbound. It's also evaluated later, when it is passed arguments. It's created with `fn`. 59 | 60 | ### Function shorthand A function written like this `(fn [x] (+ x 2))` can be rewritten like this `#(+ x 2)`, or if it has several arguments `%1`, `%2` are to be used. 61 | 62 | ### Naming functions 63 | 64 | ### All the ways to declare a function in Clojure 65 | 66 | #### Anonymous functions 67 | 68 | ```clojure 69 | (fn [x] (+ x 1)) 70 | #(+ % 1) 71 | (partial + 1) 72 | ``` 73 | 74 | #### Named functions 75 | 76 | ```clojure 77 | (def incr (fn [x] (+ x 1))) 78 | (defn incr [x] (+ x 1)) 79 | (fn incr [x] (+ x 1)) 80 | ``` 81 | 82 | ### Passing arguments to functions 83 | 84 | You can create a function that takes no argument with `[]`. 85 | 86 | It is possible to declare a function that will take different numbers of arguments: 0, 1 or more. This is how you define them, you give it a list: 87 | 88 | ```clojure 89 | (defn incr 90 | ([] 1) 91 | ([x] (+ x 1))) 92 | ``` 93 | 94 | You can also create a function that takes an unlimited number of arguments: it will lump all the "extra" arguments after `&` together in a list. Anything before the `$` is mandatory. 95 | 96 | ```clojure 97 | (defn vargs 98 | [x y & more-args] 99 | {:x x 100 | :y y 101 | :more more-args}) 102 | ``` 103 | 104 | # Other Updates 105 | 106 | - Added 12 functions to [the cheatsheet](../functions.md) 107 | - Did 7 [4clojure problems](../code/4clojure.md) 108 | 109 | # Little ClojureFam update 110 | 111 | You can now track my progress in the ClojureFam repository as well, I created [my learner card](https://github.com/athensresearch/ClojureFam/issues/27)! 112 | 113 | # Meta Commentary 114 | 115 | I already find myself going back to my notes and cheatsheet, more often than going back to the original text. In a way I trust what I have been writing, which is vastly different than when I was in school. That may be explained by the fact that I can actually go at my own pace here. 116 | -------------------------------------------------------------------------------- /posts/2020-06-24.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 1 Day 3 2 | 3 | Another day, more Clojure concepts. This is day 3 of learning Clojure in public, with Athens' ClojureFam. I'm changing the the format of these posts and will be using the one I saw in my cohort-mate Mani's repository, [learn-clojure-in-public](https://github.com/itsrainingmani/learn-clojure-in-public/blob/master/week1/june-23-2020.md), so here we go! 4 | 5 | ## Expectations 6 | 7 | Yesterday I was a little bit disappointed with my performance on 4Clojure, it took me a lot longer to complete 7 problems than the 20 I did on Monday. Difficulty goes up, so that's expected. I also don't know how to use `map` and `reduce` in Clojure so I reached for recursion every single time. So today I decided to focus on Clojure From the Ground Up. Luckily Chapter 4 covers reduce & co. as well as recursion. 8 | My initial expectation was to complete Chapter 4 and 5. However, after chatting with my cohort-mate I found out that Chapter 5 (which covers macros) can easily be skipped: it's complicated, not really necessary to contribute to Athens and I can easily do it at a later date. I would rather solidify my learning 9 | 10 | ## What I learned 11 | 12 | ### Clojure from the Ground Up -- [Chapter 4](https://aphyr.com/posts/304-clojure-from-the-ground-up-sequences) 13 | 14 | The first part of this chapter was introducing recursions which was nice to go over again, but so far I have the opposite problem: I use recursions all the time in lisps. I was happy to cover the `if` function though. 15 | 16 | This chapter mostly felt like a laundry list of functions to manipulate sequences. I added them all to my [function cheatsheet](../functions.md) and it didn't really feel necessary to document this here, it would be redundant. I did learn some new things though: 17 | For instance that strings are sequences too! You can break a string into a sequence of characters with `seq` and rebuild it with `apply` and `str` like so `(apply str (reverse "woolf"))`. 18 | 19 | I was also quite impressed with some function, which I feel I have built myself many times over and are already there in Clojure. I found out that you can leverage quite niche tools from Java as well. If you want to figure out if a letter is uppercase or not you can call `#(Character/isUpperCase %)`. 20 | Among the functions that I was happy to see, were: 21 | 22 | - `frequencies` will count how many times an element appears in a sequence. `(frequencies [:meow :mrrrow :meow :meow])` will return `{:meow 3, :mrrrow 1}` 23 | - `group-by` group sequences by a function, for instance 24 | 25 | ```clojure 26 | user=> (pprint (group-by :first [{:first "Li" :last "Zhou"} 27 | {:first "Sarah" :last "Lee"} 28 | {:first "Sarah" :last "Dunn"} 29 | {:first "Li" :last "O'Toole"}])){"Li" [{:last "Zhou", :first "Li"} {:last "O'Toole", :first "Li"}], 30 | "Sarah" [{:last "Lee", :first "Sarah"} {:last "Dunn", :first "Sarah"}]} 31 | ``` 32 | 33 | - And of course the famed `reduce`! It takes a function and will run it on the first two elements (unless you pass a starting value) and then run again on the result of that function and the next value. `(reduce f default-value coll)` 34 | 35 | At the end of the chapter, the book walks you through building a function to find the sum of the products of consecutive pairs of the first 1000 odd integers. Here is my answer with a lot of help from the book: 36 | 37 | ```clojure 38 | (reduce + 39 | (take 1000 40 | (map (fn [pair] (* (first pair) (second pair))) 41 | (partition 2 1 (filter odd? (iterate inc 0)))))) 42 | ``` 43 | 44 | #### The difference between collections and sequences 45 | 46 | Every sequence is a collection, but not every collection is a sequence. 47 | The `seq` function makes it possible to convert a collection into a sequence. Any object supporting `first` and `rest` functions is a sequence. 48 | 49 | #### Problems from Chapter 4 50 | 51 | Chapter 4 of Clojure From the Ground Up is the first one that comes with exercises. Here are my answers and choices for the first three. I decided to skip the prime number question because there's probably better use of my clojure learning time. 52 | 53 | ##### Write a function to find out if a string is a palindrome–that is, if it looks the same forwards and backwards. 54 | 55 | The first idea that I get here is always to use a recursion. I know I won't have any issue so I looked for another solution. 56 | An easy one that is almost cheating is reversing the string and making sure it matches: 57 | 58 | ```clojure 59 | (defn palindrome? [word] 60 | (= word (apply str (reverse word)))) 61 | ``` 62 | 63 | ##### Find the number of ‘c’s in “abracadabra”. 64 | 65 | I can find of a few solutions for this one. How about using `frequencies` that I liked so much when I read about it in the chapter? 66 | 67 | ```clojure 68 | (defn countc [string] 69 | (last (first 70 | (filter (fn [pair] (= (first pair) \c)) 71 | (frequencies string))))) 72 | ``` 73 | 74 | Another easy way would be to keep the c's only and the count the members of the sequence: 75 | 76 | ```clojure 77 | (defn countc [string] 78 | (count 79 | (filter (fn [l] (= l \c)) 80 | (seq string)))) 81 | ``` 82 | 83 | ##### Write your own version of filter. 84 | 85 | ```clojure 86 | (defn my-filter [f xs] 87 | (reduce 88 | (fn [v e] 89 | (if (f e) (conj v e) v)) 90 | [] xs)) 91 | 92 | (my-filter pos? [1 2 -1 1 0 1 -1]) 93 | ``` 94 | 95 | ### 4clojure 96 | 97 | Today I did 16 4clojure problems which brings my total number of complete problems to 43! I have added my answers to the [4clojure](../code/4clojure.md) document. 98 | 99 | ## Takeaways 100 | 101 | Even though I decided to skip Chapter 5 for now and didn't match my expectations, I am glad I did. I have taken in a lot information in the past few days. I wouldn't want to overload myself with too much, and time spent applying this new knowledge is probably better spent. It's not a sprint, but a marathon! I will make sure over the next few days to spend some time to review and make flash cards. This will most likely be tomorrow and over the weekend when I will either have less time, or will be away from a computer. 102 | 103 | Today has been the most active day for our ClojureFam cohort: we've been exchanging tips, solutions and encouragement. I hope this continues like this. 104 | 105 | Let's end the day with a tally of what I completed: 106 | 107 | - Chapter 4 of Clojure From the Group Up 108 | - 3 problems from that chapter 109 | - 16 4clojure problems 110 | -------------------------------------------------------------------------------- /posts/2020-06-25.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 1 Day 4 (4/35) 2 | 3 | ## Expectations 4 | 5 | After a good day on yesterday (Chapter 4 of Clojure from the Ground Up, its exercises and 16 4clojure problems), the expectation for today is more limited as I am constrained by time. I expect to read part of Chapter 6 (skipping Chapter 5, macros, for now as it's not that relevant to the Athens codebase) Whatever I can achieve will be a little victory. 6 | 7 | ## What I learned (mostly from Chapter 6 of Clojure from the Ground Up) 8 | 9 | ### Scope of `let` 10 | 11 | Declaration with let can be used outside of their original expressions as illustrated by this example: 12 | 13 | ```clojure 14 | user=> (let [x 1] 15 | (prn (inc x)) 16 | (prn (inc x))) 17 | 2 18 | 2 19 | ``` 20 | 21 | ### Closures 22 | 23 | I was happy to see that closure's, like in JavaScript, also made it to Clojure. Functions will remember the symbols from the time they were constructed. They will reach out to their lexical scope like in this example: 24 | 25 | ```clojure 26 | (defn present 27 | [gift] 28 | (fn [] gift)) 29 | 30 | user=> (def red-box (present "plush tiger")) 31 | #'user/red-box 32 | user=> (red-box) 33 | "plush tiger" 34 | ``` 35 | 36 | In this case, `present` creates a new function that will always reach for the value of gift, passed at the function creation. 37 | 38 | ### Delays 39 | 40 | It is possible to delay a function's execution, by using `let`. `(def later (fn [] (prn "Adding") (+ 1 2)))` will only return `3` when the function is called like this `(later)`. 41 | There's even a standard macro for this called `delay` that you can use like this `(def later (delay (prn "Adding") (+ 1 2)))` and it has to be called with `defer`, like so `(deref later)`, or with the shorthand `@later`. 42 | 43 | ### Futures 44 | 45 | Futures delegates computation to a different thread, to be evaluated when possible. It's evaluated in parallel. Futures return immediately and give us the identity which will point to the value of the last expression in the future. 46 | 47 | ```clojure 48 | user=> (def x (future (prn "hi") (+ 1 2))) 49 | "hi" 50 | #'user/x 51 | user=> (deref x) 52 | 3 53 | ``` 54 | 55 | Evaluation of threads is concurrent and it is possible that expressions will be evaluated out of order. 56 | To quote the author, "Futures are the most generic parallel construct in Clojure. You can use futures to do CPU-intensive computation faster, to wait for multiple network requests to complete at once, or to run housekeeping code periodically." This is something that I have not really seen before, coming from JavaScript. The closest in JavaScript would probably using web workers (in the browser). 57 | 58 | ### Promises 59 | 60 | You can defer execution, pass a value, and then deref it. It guarantees that any attempt to read the value will wait until the value has been written. We can use promises to synchronize a program which is being evaluated concurrently. 61 | A simple example of `promise` would go like this: 62 | 63 | ```clojure 64 | (def box (promise)) 65 | (deliver box :maceo) 66 | (deref box) ; returns :maceo 67 | ``` 68 | 69 | There was an interesting example using a `promise` and a `future`, like this: 70 | 71 | ```clojure 72 | (def card (promise)) 73 | (def dealer (future 74 | (Thread/sleep 5000) 75 | (deliver card [(inc (rand-int 13)) 76 | (rand-nth [:clubs :spades :hearts :diamonds])]))) 77 | ``` 78 | 79 | It indeed delivered on that promise of delivering the... promise. With a (deref card) you fill it and delegate it to another thread. 80 | In summary: 81 | 82 | - delays defer evaluation 83 | - futures parallelize it 84 | - promises are concurrent "without specifying how the evaluation occurs", we provide the value, when we have it 85 | 86 | ### Vars 87 | 88 | So we covered vars [back on Tuesday](./2020-06-23.md#vars). As a reminder, vars are mutable variables and use the `def` keyword to be created. They can be updated. 89 | 90 | ```clojure 91 | (def x :mouse) 92 | (def box (fn [] x)) 93 | (def x :cat) 94 | (box) ; will return :mouse 95 | ``` 96 | 97 | A reference is the same everywhere and is called a global variable. 98 | 99 | #### Dynamic vars 100 | 101 | A dynamic var reference can only be overridden in one particular scope. The convention is to name them with asterisks around their names, like so `(def ^:dynamic *board* :maple)`. 102 | 103 | ```clojure 104 | (def ^:dynamic *board* :maple) 105 | (defn cut [] (prn "sawing through" *board*)) 106 | (cut) ; "sawing through" :maple 107 | 108 | (binding [*board* :cedar] (cut)) ; will return "sawing through" :cedar, this is specific to this scope 109 | (cut) ; otherwise the value remains the same => "sawing through" :maple 110 | ``` 111 | 112 | Within the `binding` expression the value of `*board*` has changed, but it remains the same outside of it. 113 | `fn` and `let` create immutable lexical scope, binding creates a dynamic scope. The dynamic scope propagates through function calls when the lexical scope is limited to the literal text of the `fn` or `let`. 114 | When writing actual code, one should use `def` sparingly. 115 | 116 | ## Takeways 117 | 118 | Today I did quite a lot with the time that I had. I had a good introduction to concurrency in Clojure with vars, `delay`, `future` and `promises`. I am looking forward to learning about atoms and refs tomorrow. 119 | Let's end the day with a tally of what I completed (which is much more than I anticipated!): 120 | 121 | - Most of Chapter 6 of Clojure From the Group Up 122 | - 3 4clojure problems 123 | - Still wrote more ~800 words, only 200 less than previous days 124 | -------------------------------------------------------------------------------- /posts/2020-06-26.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 1 Day 5 (5/35) 2 | 3 | ## Expectations 4 | 5 | After yesterday where I got started on Chapter 6 in Clojure from the Group Up (where I did much more than I thought I would), today I expected to finish what I started. That meant reading the last part of Chapter 6, completing the exercises (the ones in Chapter 4 were difficult, let's see about these), and use any remaining time to do more 4clojure problems. The [next checkpoint](https://github.com/athensresearch/ClojureFam/issues/27) in terms of problems is 60, and I am 17 short so I expect to reach that milestone early next week. 6 | 7 | ## What I Learned 8 | 9 | ### Atoms 10 | 11 | Atoms store a value. You can access the value held in an atom with `(defer xs)`, or the shorthand `@xs`. 12 | One can set the value of an atom with `reset!`, like so `(reset! xs :foo)`. 13 | The value can be updated with `swap!`, but swap takes a pure function that takes in the current value and returns the updated value, but you can also call `(swap! x + 5 6)` which will call `(+ 5 6)` to find the new value. 14 | Interestingly a thread-safe version of `(dotimes [i 10] (future (def xs (conj xs i))))` becomes `(dotimes [i 10] (future (swap! xs conj i)))` (after `def`ing the vars). 15 | It's thread-safe, linearizable and a great way to represent state. 16 | 17 | ### Refs 18 | 19 | Refs are for multi-identity updates. `(def x (ref 0))`. `ref-set` sets, `alter` updates and both need to be wrapped in a `dosync` expression. 20 | 21 | ```clojure 22 | user=> (def x (ref 0)) 23 | user=> (def y (ref 0)) 24 | user=> (dosync 25 | (ref-set x 1) 26 | (ref-set y 2)) 27 | 2 28 | user=> [@x @y] 29 | [1 2] 30 | 31 | user=> (dosync 32 | (alter x + 2) 33 | (alter y inc)) 34 | 3 35 | user=> [@x @y] 36 | [3 3] 37 | ``` 38 | 39 | `commute` is a better alternative if the order of operations doesn't matter. 40 | `ensure` lets you read values from one ref to compute the other 41 | 42 | ```clojure 43 | user=> (dosync 44 | (alter x + (ensure y))) 45 | ``` 46 | 47 | Refs are slower than atoms and should only be used when updating multiple pieces of state. 48 | 49 | ### Summary table for future reference 50 | 51 | - **Symbols** are immutable, transparent and their scope is lexical 52 | - **Vars** are mutable, transparent, unrestricted (for updates) and their scope is dynamic 53 | - **Delays** are mutable, their read is blocking, updates are only once, and their evaluation is lazy 54 | - **Futures** are mutable, blocking, only once and their evaluation is parallel 55 | - **Promises** are mutable, blocking and updated only once 56 | - **Atoms** are mutable, non blocking, and linearizable 57 | - **Refs** are mutable, non blocking and serializable 58 | 59 | ### Exercises from Clojure from the Ground Up (Chapter 6) 60 | 61 | We start with this program to compute the sum of the first 10,000,000 numbers: 62 | 63 | ```clojure 64 | user=> (defn sum [start end] (reduce + (range start end))) 65 | user=> (time (sum 0 1e7)) 66 | "Elapsed time: 1001.295323 msecs" 67 | 49999995000000 68 | ``` 69 | 70 | #### 1. Use delay to compute this sum lazily; show that it takes no time to return the delay, but roughly 1 second to deref. 71 | 72 | We have covered how to set the delay earlier in this chapter. 73 | 74 | ```clojure 75 | user=> 76 | (defn sum [start end] (reduce + (range start end))) 77 | #'user/sum 78 | user=> 79 | (time (def later (delay (sum 0 1e7)))) 80 | "Elapsed time: 0.223565 msecs" 81 | #'user/later 82 | user=> 83 | (time (deref later)) 84 | "Elapsed time: 907.876844 msecs" 85 | 49999995000000 86 | ``` 87 | 88 | #### 2 We can do the computation in a new thread directly, using (.start (Thread. (fn [] (sum 0 1e7)))–but this simply runs the (sum) function and discards the results. Use a promise to hand the result back out of the thread. Use this technique to write your own version of the future macro. 89 | 90 | ```clojure 91 | user=> 92 | (def promise-sum (promise)) 93 | #'user/promise-sum 94 | user=> 95 | (.start (Thread. (fn [] (deliver promise-sum (sum 0 1e7))))) 96 | nil 97 | user=> 98 | (deref promise-sum) 99 | 49999995000000 100 | ``` 101 | 102 | And for my own version of futures, as a reminder "futures return immediately, and give us an **identity** which will point to the value of the last expression in the future". So we need to return a function that will deref the promise: 103 | 104 | ```clojure 105 | (defn my-future [promise f & args] 106 | (do 107 | (.start (Thread. (deliver promise (f args)))) 108 | (fn [] (deref promise)))) 109 | ``` 110 | 111 | Here is how you use it: 112 | 113 | ```clojure 114 | user=> (def promise1 (promise)) 115 | #'user/promise1 116 | user=> (defn my-future [promise f & args] 117 | (do 118 | (.start (Thread. (deliver promise (f args)))) 119 | (fn [] (deref promise)))) 120 | #'user/my-future 121 | user=> (def get-value (my-future promise1 count 1)) 122 | #'user/get-value 123 | user=> 124 | (get-value) 125 | 1 126 | ``` 127 | 128 | #### 3 If your computer has two cores, you can do this expensive computation twice as fast by splitting it into two parts: (sum 0 (/ 1e7 2)), and (sum (/ 1e7 2) 1e7), then adding those parts together. Use future to do both parts at once, and show that this strategy gets the same answer as the single-threaded version, but takes roughly half the time. 129 | 130 | ````clojure 131 | (time (let [a (future (sum 0 (/ 1e7 2))) 132 | b (future (sum (/ 1e7 2) 1e7))] 133 | (+ @a @b)))``` 134 | And the results: 135 | ```clojure 136 | user=> (time (let [a (future (sum 0 (/ 1e7 2))) 137 | b (future (sum (/ 1e7 2) 1e7))] 138 | (+ @a @b))) 139 | "Elapsed time: 590.256155 msecs" 140 | 4.9999995E13 141 | ```` 142 | 143 | #### 4 Instead of using reduce, store the sum in an atom and use two futures to add each number from the lower and upper range to that atom. Wait for both futures to complete using deref, then check that the atom contains the right number. Is this technique faster or slower than reduce? Why do you think that might be? 144 | 145 | ```clojure 146 | (defn my-sum [start end] 147 | (let [result (atom 0)] 148 | (let 149 | [a (future (doseq [i (range start (/ end 2))] (swap! result + i))) 150 | b (future (doseq [i (range (/ end 2) end)] (swap! result + i)))] @a @b) 151 | @result)) 152 | ``` 153 | 154 | #### 5 Write a function which, in a dosync transaction, removes the first number in work and adds it to sum. Then, in two futures, call that function over and over again until there’s no work left. Verify that @sum is 4999950000. Experiment with different combinations of alter and commute–if both are correct, is one faster? Does using deref instead of ensure change the result? 155 | 156 | ```clojure 157 | (def work (ref (apply list (range 1e5)))) 158 | (def sum (ref 0)) 159 | 160 | (defn dowork [] 161 | (dosync 162 | (alter sum + (first @work)) 163 | (alter work rest) 164 | (count @work))) 165 | 166 | (while (pos? (count @work)) dowork) 167 | ``` 168 | 169 | For this exercise, I got most of it to work, however the while loop hangs and I am not sure what the best solution is for the future. 170 | 171 | ## Always use the local REPL over an online one 172 | 173 | I've been cheating and I have been using the repl.it one for quick experimentation. I know that I would get autocomplete and a lot of other little benefits that I don't get online (the repl.it... REPL is pretty barebone), but when my laptop is driving two 1440p screens, Roam for the notes, other chrome tabs, adding VS Code with Calva to the lot it is a little bit too much. Well, I will probably not do that anymore as I have encountered many issues and programs that are supposed to run without any issue, just don't online. Lesson learned. It took me forever to complete the first exercise, thinking my code was wrong, when it was in fact the REPL faulting me. 174 | 175 | ## Takeaways 176 | 177 | This chapter was quite confusing and I definitely need to give it another read to solidity some of this knowledge (and to make sense of it) -- but I am glad I did it. 178 | 179 | Let's end the day with a tally of what I completed (which is much more than I anticipated!): 180 | 181 | - Completed Chapter 6 of Clojure From the Ground Up and its problems (that wasn't easy!) 182 | - No 4clojure problem today :( 183 | - Wrote my longest update, with almost 1260+ words (code snippets help) 184 | See you tomorrow! 185 | -------------------------------------------------------------------------------- /posts/2020-06-27.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 1 Day 6 (6/35) 2 | 3 | ## Expectations 4 | 5 | For today I had very low expectations as I am travelling. The goal was to read some of Chapter 7 (which covers the way a Clojure project is structured -- it's called logistics for a reason). It's a long one too so the expectation is not to finish it. I expect to take very little notes as it is more akin to discover how the projects are structured, rather than learn anything. 6 | 7 | ## What I learned 8 | 9 | I tagged along this chapter by creating a new folder `./code/cftgu-chapter7/`. 10 | 11 | ### Namespaces 12 | 13 | A namespace is set like this `(ns namespace-name.core)`. This will dictate how to retrieve symbols. A symbol is always accessible by prepending the namespace name as well: `(namespace-name/foo)`. 14 | 15 | Note that namespaces automatically include `clojure.core` which includes all the standard functions. 16 | 17 | To set a namespace and include another one as well one can use `(ns user (:require [scratch.core]))`. It means that the user namespace needs access to `scratch.core`. It's also possible to give a shortname to an imported namespace like this `(**ns **user (:require [scratch.core :as c]))`. It's even possible to avoid writing the namespace entirely for a specific function by importing it like this `(ns user (:require [scratch.core :refer [foo]]))`: `foo` will be accessible without the namespace. 18 | 19 | ### Exploring data 20 | 21 | To explore JSON in Clojure we use a library called Cheshire that we get from the NPM of Clojure, Clojars. We just need to add it to the dependencies in `project.clj`. 22 | 23 | ````clojure 24 | user=> (use 'cheshire.core) 25 | user=> (first (parse-string (slurp "2008.json") true)) 26 | {:robbery 22, 27 | :aggravated_assaults 33, 28 | :coverage_indicator 97, 29 | :drug_abuse_violationstotal 203,``` 30 | So we got this array of objects and they all have the same properties. We can get the values of all of the same keyword: 31 | ```clojure 32 | user=> (->> data (map :driving_under_influence)) 33 | (198 34 | 1095 35 | 114 36 | 98 37 | ...) 38 | ```` 39 | 40 | To get the max `user=> (->> data (map :driving_under_influence) (apply max))`, or to get the n largest (10): 41 | 42 | ```clojure 43 | user=> (->> data (map :driving_under_influence) sort (take-last 10)) 44 | (8589 10432 10443 10814 11439 13983 17572 18562 26235 45056) 45 | ``` 46 | 47 | To get the frequencies, `user=> (->> data (map :driving_under_influence) frequencies)`. 48 | 49 | To produce some sort of histogram: 50 | 51 | ```clojure 52 | user=> (->> data (map :driving_under_influence) frequencies (sort-by key) pprint) 53 | ([0 227] 54 | [1 24] 55 | [2 17] 56 | [3 20] 57 | ``` 58 | 59 | It means that 227 counties report no DIUs. 60 | 61 | We can "zoom out" of the data set and see the other key values too: 62 | 63 | ```clojure 64 | user=> (->> data (sort-by :driving_under_influence) (take-last 10) pprint) 65 | ({:other_assaults 3096, 66 | :gambling_all_other 3, 67 | :arson 106, 68 | :have_stolen_property 698, 69 | ``` 70 | 71 | We use `mapcat` to map and concatenate at the same time. 72 | 73 | ```user=> (->> data (sort-by :driving_under_influence) (take-last 10) (mapcat keys) (into (sorted-set)) pprint)#{:aggravated_assaults :all_other_offenses_except_traffic :arson 74 | :auto_thefts :bookmaking_horsesport :burglary :county_population 75 | :coverage_indicator :curfew_loitering_laws :disorderly_conduct 76 | ``` 77 | 78 | We can extract several keywords at the same time: 79 | 80 | ```clojure 81 | user=> (->> data (sort-by :driving_under_influence) (take-last 10) (map #(select-keys % [:driving_under_influence :fips_county_code :fips_state_code])) pprint) 82 | ({:fips_state_code "06", 83 | :fips_county_code "067", 84 | :driving_under_influence 8589} 85 | {:fips_state_code "48", 86 | :fips_county_code "201", 87 | :driving_under_influence 10432} 88 | ``` 89 | 90 | ## Takeaways 91 | 92 | Today I learned the basics of setting up a Clojure project, how namespaces work and the basics of Clojure testing. Nothing very complicated which was perfect for today. I am glad we covered tests (I don't like writing them, at all, but they serve a purpose). 93 | 94 | Also got an introduction of how to process JSON and extract the relevant information. I'm looking forward to finishing this chapter tomorrow. 95 | 96 | Let's end the day with a tally of what I completed: 97 | 98 | - More than half of Chapter 7 of Clojure From the Ground Up 99 | - Setting up a dummy project 100 | -------------------------------------------------------------------------------- /posts/2020-06-28.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 1 Day 7 (7/35) 2 | 3 | ## Expectations 4 | 5 | I was travelling again today so my expectation was to complete Chapter 7 and maybe tackle the exercises. I am very happy to report that I was able to finish the chapter, do the exercises (despite some little setup issues with the scratch project) and on top of that do 3 4clojure problems! 6 | 7 | ## What I learned 8 | 9 | Yesterday, I finished with getting the driving under the influence data per county. 10 | 11 | ```clojure 12 | user=> (->> data (sort-by :driving_under_influence) (take-last 10) (map #(select-keys % [:driving_under_influence :fips_county_code :fips_state_code])) pprint) 13 | ({:fips_state_code "06", 14 | :fips_county_code "067", 15 | :driving_under_influence 8589} 16 | ``` 17 | 18 | I then import `fips.json`, our goal being to use it to get the county names. `keys` will return the keys' names for the data. 19 | 20 | ```clojure 21 | user=> (keys fips) 22 | (:table) 23 | user=> (keys (:table fips)) 24 | (:columnNames :columnTypes :rows) 25 | user=> (->> fips :table :columnNames) 26 | ["FIPS" "Name"] 27 | user=> (->> fips :table :rows (take 3) pprint) 28 | (["02000" "AK"] 29 | ["02013" "AK, Aleutians East"] 30 | ["02016" "AK, Aleutians West"]) 31 | ``` 32 | 33 | After loading `fips.json`, we can start playing in the REPL again: 34 | 35 | ```clojure 36 | user=> (use 'scratch.crime :reload) 37 | nil 38 | user=> (fips "10001") 39 | "DE, Kent" 40 | ``` 41 | 42 | After updating `crime.clj`, we can do: 43 | 44 | ```clojure 45 | user=> (use 'scratch.crime :reload) (pprint (most-duis "2008.json")) 46 | ({:fips_state_code "06", 47 | :fips_county_code "067", 48 | :driving_under_influence 8589} 49 | {:fips_state_code "48", 50 | :fips_county_code "201", 51 | :driving_under_influence 10432} 52 | ``` 53 | 54 | After our updates to crime.clj: 55 | 56 | ```clojure 57 | user=> (use 'scratch.crime :reload) (pprint (most-duis "2008.json")) 58 | nil 59 | {"CA, Orange" 17572, 60 | "CA, San Bernardino" 13983, 61 | "CA, Los Angeles" 45056, 62 | "CA, Riverside" 10814, 63 | ``` 64 | 65 | ### Other notes 66 | 67 | I wasn't able to import all the symbols from other namespaces in my test files so I had to explicitly list them: 68 | 69 | ```clojure 70 | (ns cftgu-chapter6.crime-test 71 | (:require [clojure.test :refer [deftest is]] 72 | [cftgu-chapter6.crime :refer [fips-code]])) 73 | ``` 74 | 75 | ### Chapter 7 Problems 76 | 77 | #### Problem 1 78 | 79 | ```clojure 80 | (defn prevalence-of-duis 81 | "Prevalence of DUIs." 82 | [file] 83 | (->> file 84 | load-json 85 | (map (fn [county] 86 | {:county (fips (fips-code county)) 87 | :duis (:driving_under_influence county) 88 | :population (:county_population county) 89 | :prevalence (double (if 90 | (pos? (:county_population county)) 91 | (/ (:driving_under_influence county) (:county_population county)) 92 | 0))})) 93 | (sort-by :prevalence) 94 | (take-last 10))) 95 | ``` 96 | 97 | #### Problem 2 98 | 99 | ```clojure 100 | (defn most-duis 101 | "Given a JSON filename of UCR crime data for a particular year, finds the counties with the most DUIs." 102 | [file] 103 | (->> file 104 | load-json 105 | (sort-by :driving_under_influence) 106 | (take-last 10) 107 | (map (fn [county] 108 | [(fips (fips-code county)) 109 | [:duis (:driving_under_influence county) 110 | :population (:county_population county) 111 | :report-count (:grand_total county) 112 | :prevalence (double (/ (:driving_under_influence county) (:county_population county)))]])) 113 | (into {}))) 114 | ``` 115 | 116 | #### Problem 3 117 | 118 | ```clojure 119 | (defn most-prevalent 120 | "Given a JSON filename of UCR crime data for a particular year, and a crime, find the counties with the highest prevalence of this crime." 121 | [file crime] 122 | (->> file 123 | load-json 124 | (map (fn [county] 125 | {:county (fips (fips-code county)) 126 | :occurrence (crime county) 127 | :population (:county_population county) 128 | :prevalence (double (if 129 | (pos? 130 | (:county_population county)) 131 | (/ 132 | (crime county) 133 | (:county_population county)) 134 | 0))})) 135 | (sort-by :prevalence) 136 | (take-last 10))) 137 | ``` 138 | 139 | ## Takeaways 140 | 141 | The difficulty in this part lies with setting up the project properly: you can come up with the right code and still get errors. For instance I was not able to have the tests and the code (in the REPL) work at the same time. It's one or the other, not the two at the same time. 142 | 143 | - [Working tests](https://github.com/alaq/learning-clojure-in-public/tree/b5671e7eaf57d271c9ed9d156d5684142d1ec530) 144 | - [Working REPL](https://github.com/alaq/learning-clojure-in-public/tree/6bc7bce90f2b0d7acd63ab01a0ebb847844c76c1) 145 | 146 | The second difficulty is understanding how to process the JSON itself. The map function is useful but I will definitely have to cover the material again to properly understand it and be able to apply it with different data sets. 147 | 148 | Let's end the day with a tally of what I completed: 149 | 150 | - Finished Chapter 7 of Clojure From the Ground Up 151 | - Solved the problems at the end of Chapter 7 152 | - 3 4clojure problems (I'm getting back in the groove!) 153 | -------------------------------------------------------------------------------- /posts/2020-06-29.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 2 Day 1 (8/35) 2 | 3 | Week 1 is now over, now onto week 2! I think I covered a lot of ground last week (6 chapters of Clojure from the Group Up, 49 4clojure problems). I hope to do the same this week. 4 | 5 | It feels like I started just yesterday (almost true!) but I've already learned a lot. I am also making a lot of progress in how I synthesize my learning and am able to produce reusable (at least for myself) content. I mostly refer to my own notes at this point when I am trying to solve a problem. 6 | 7 | ## Expectations 8 | 9 | The expectation for today was to cover some of Chapter 8 of Clojure from the Ground Up (maybe half since it's a long chapter), and then solve some 4clojure problems. I was aiming at the lofty goal of completing 11 new ones to get my total to 60 because it's the next step in my ClojureFam checkpoint but it looks that it will be for later this week. 10 | 11 | ## What I learned 12 | 13 | In Chapter 8 of Clojure from the Ground Up, we went to space! We built a rocket. 14 | 15 | You can see the code I wrote [here](../code/scratch/src/scratch/rocket.clj) and the tests [here](../code/scratch/test/scratch/rocket_test.clj). 16 | 17 | ```clojure 18 | user=> (use 'scratch.rocket :reload) 19 | nil 20 | user=> (atlas-v) 21 | {:dry-mass 50050, :fuel-mass 284450, :time 0, :isp 3050, :max-fuel-rate 285550/253, :max-thrust 4152000.0} 22 | user=> (-> (atlas-v) prepare clojure.pprint/pprint) 23 | {:dry-mass 50050, 24 | :fuel-mass 284450, 25 | :time 0, 26 | :isp 3050, 27 | :max-fuel-rate 285550/253, 28 | :max-thrust 4152000.0, 29 | :position {:x 6378137, :y 0, :z 0}, 30 | :velocity {:x 0, :y 463.8312116386399, :z 0}} 31 | nil 32 | ``` 33 | 34 | Then running this in the REPL, caused a NullPointerException. I have had trouble debugging error when trying to solve 4clojure problems so every little bit of information will be very helpful. 35 | 36 | ```clojure 37 | user=> 38 | (-> (atlas-v) prepare (step 1) clojure.pprint/pprint) 39 | Execution error (NullPointerException) at scratch.rocket/step (rocket.clj:128). 40 | null 41 | class java.lang.NullPointerException 42 | ``` 43 | 44 | I wasn't able to print the stack with `(pst *e)` like in the examples. 45 | 46 | I also didn't realize that on Calva you can just click "All" and you will see the stack. Quite useful! 47 | 48 | After fixing the issue I was able to run the functions in the REPL: 49 | 50 | ```clojure 51 | user=> (use 'scratch.rocket :reload) 52 | nil 53 | user=> (-> (atlas-v) prepare (step 1) clojure.pprint/pprint) 54 | 6378137.0 55 | {:time 0, 56 | :isp 3050, 57 | :fuel-mass 71680300/253, 58 | :max-fuel-rate 285550/253, 59 | :dry-mass 50050, 60 | :max-thrust 4152000.0, 61 | :position {:x 0, :y 463.8312116386399, :z 0}, 62 | :t 1, 63 | :velocity 64 | {:x 0.491184411870704, :y 463.8312116386399, :z -9.799999999999999}} 65 | nil 66 | ``` 67 | 68 | With some difficulty I was able to match the results of the author and plot the trajectory of the rocket: 69 | 70 | ```clojure 71 | user=> 72 | (->> (atlas-v) prepare (trajectory 1) (take 3) clojure.pprint/pprint) 73 | ({:dry-mass 50050, 74 | :fuel-mass 284450, 75 | :time 0, 76 | :isp 3050, 77 | :max-fuel-rate 285550/253, 78 | :max-thrust 4152000.0, 79 | :position {:x 6378137, :y 0, :z 0}, 80 | :velocity {:x 0, :y 463.8312116386399, :z 0}} 81 | {:dry-mass 50050, 82 | :fuel-mass 71680300/253, 83 | :time 1, 84 | :isp 3050, 85 | :max-fuel-rate 285550/253, 86 | :max-thrust 4152000.0, 87 | :position {:x 6378137, :y 463.8312116386399, :z 0}, 88 | :velocity 89 | {:x 0.491184411870704, 90 | :y 463.8312116386399, 91 | :z -6.000769315822031E-16}} 92 | {:dry-mass 50050, 93 | :fuel-mass 71394750/253, 94 | :time 2, 95 | :isp 3050, 96 | :max-fuel-rate 285550/253, 97 | :max-thrust 4152000.0, 98 | :position 99 | {:x 6378137.491184412, 100 | :y 927.6624232772798, 101 | :z -6.000769315822031E-16}, 102 | :velocity 103 | {:x 1.0172105016106543, 104 | :y 463.83049896253056, 105 | :z -1.2001538631644063E-15}}) 106 | nil 107 | ``` 108 | 109 | And even the altitude of the rocket: 110 | 111 | ```clojure 112 | user=> 113 | (->> (atlas-v) prepare (trajectory 1) (map altitude) (take 10) clojure.pprint/pprint) 114 | (0.0 115 | 0.016865378245711327 116 | 0.5586459208279848 117 | 1.660183128900826 118 | 3.3565550493076444 119 | 5.683078692294657 120 | 8.675312489271164 121 | 12.36905876826495 122 | 16.80036628153175 123 | 22.005532761104405) 124 | nil 125 | ``` 126 | 127 | ## Takeaways 128 | 129 | It was a lot of math, but it was also very instructive on how to make functions work together. Each function serves one purpose and chaining them one after another gets you where you need to go. I look forward to completing the rest of the program tomorrow. 130 | 131 | Weirdly enough the difficulty lied in making the functions return the same output as the author's, as I have been typing all the functions by hand (which helps understanding but introduces bugs that are hard to debug). 132 | 133 | Sadly I didn't complete any 4clojure today. I hope that things will be different tomorrow. 134 | -------------------------------------------------------------------------------- /posts/2020-06-30.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 2 Day 2 (9/35) 2 | 3 | ## Expectations 4 | 5 | Today the expectation was to finish the rocket that I started building yesterday, in Chapter 8. I also wanted to solve problem 28 on 4clojure. I have been trying in passing for the past couple of days, starting from scratch, but each time hitting errors that I have trouble debugging. A third time's the charm! Any other 4clojure problem I do is going to be a great bonus. 6 | 7 | ## What I learned 8 | 9 | As building the rocket was probably the most complex program I have written in Clojure (yes, as much as possible I have tried to re-write the functions instead of copy/pasting them -- which has made debugging much harder...), I have learned that you cannot depend on a function that hasn't been defined beforehand. There is no hoisting like in JavaScript. Where a function is defined does matter. 10 | 11 | You can however use `(declare function-name)` at the top of the file to tell Clojure that this function will eventually be defined in this file, at a later time. 12 | 13 | ### My first non-trivial program 14 | 15 | This was the largest program I wrote to date with Clojure. I didn't really focus on the math: it's not too complicated and it's possible to figure it out with some effort, it was not really the goal here. So I took the words of the author as truth and simply tried to implement the functions without looking (sometimes, not most of the time). In the end I didn't learn a lot of new functions but I did learn a lot of tips and tricks on how to structure a program in clojure. 16 | 17 | It was interesting working with immutable data structures and passing them to pure functions, which is a big departure from what I'm used to (Object Oriented Programming). 18 | 19 | ### Chapter 8 problems 20 | 21 | Unfortunately I had trouble making the example code work (and the code from the source also had issues) so I was only minimally able to complete the problems in this chapter. 22 | 23 | #### 2. We assumed the force of gravity resulted in a constant 9.8 meter/second/second acceleration towards the earth, but in the real world, gravity falls off with the [inverse square law](http://en.wikipedia.org/wiki/Newton's_law_of_universal_gravitation). Using the mass of the earth, mass of the spacecraft, and Newton’s constant, refine the gravitational force used in this simulation to take Newton’s law into account. How does this affect the apoapsis? 24 | 25 | Here we're trying to use the inverse square law to determine the force. The force is F = G _ m1 _ m2/r^2. This is what we had originally: 26 | 27 | ```clojure 28 | (defn gravity-force 29 | "The force vector, each component in Newtons, due to gravity." 30 | [craft] 31 | ; Since force is mass times acceleration... 32 | (let [total-force (* g (mass craft))] 33 | (-> craft 34 | ; Now we'll take the craft's position 35 | :position 36 | ; in spherical coordinates, 37 | cartesian->spherical 38 | ; replace the radius with the gravitational force... 39 | (assoc :r total-force) 40 | ; and transform back to Cartesian-land 41 | spherical->cartesian))) 42 | ``` 43 | 44 | I am assuming that the distance between Earth and the rocket is going to be between the two centers, and Earth's center is at `{:x 0, :y: 0, :z 0}`. We can define a helper function that will compute that distance: 45 | 46 | ```clojure 47 | (defn distance-from-earth 48 | [coordinates] 49 | (Math.sqrt (+ (pow (:x coordinates) 2) 50 | (pow (:y coordinates) 2) 51 | (pow (:z coordinates) 2)))) 52 | ``` 53 | 54 | So we can redefine the function computing the force as such: 55 | 56 | ```clojure 57 | (defn gravity-force 58 | "The force vector, each component in Newtons, due to gravity." 59 | [craft] 60 | (let [total-force (/ (* g (mass craft) 5.97219e24) (distance-from-earth (:position craft)))] 61 | (-> craft 62 | ; Now we'll take the craft's position 63 | :position 64 | ; in spherical coordinates, 65 | cartesian->spherical 66 | ; replace the radius with the gravitational force... 67 | (assoc :r total-force) 68 | ; and transform back to Cartesian-land 69 | spherical->cartesian))) 70 | ``` 71 | 72 | #### 3. We ignored the atmosphere, which exerts drag on the craft as it moves through the air. Write a basic air-density function which falls off with altitude. Make some educated guesses as to how much drag a real rocket experiences, and assume that the drag force is proportional to the square of the rocket’s velocity. Can your rocket still reach orbit? 73 | 74 | For this function we're going to assume that the drag is proportional to the square of the rocket's velocity, which we already have. So this is completely out of nowhere and not at all researched, but let's say the drag is taking into account the density of the atmosphere, a drag coefficient (0.04 for a streamlined body, and we assume both crafts have the same drag coefficient), the area against which this atmosphere is dragging and the square of the velocity. The highest we are the less atmosphere, the less dense the atmosphere. So this formula here could be a variable of only the altitude and the velocity of the craft. Let's say that the top of the mesophere (85km from the surface of the Earth) is when the atmosphere stops having a drag. So now we can write the function as such: 75 | 76 | ```clojure 77 | (defn drag 78 | [velocity craft] 79 | (let [[v2 (+ (pow (:x velocity) 2) 80 | (pow (:y velocity) 2) 81 | (pow (:z velocity) 2))] 82 | [density (max 0 (- 85000 (altitude craft)))]](* 0.5 0.04 10 ()))) 83 | ``` 84 | 85 | This is definitely something I want to get back to after ClojureFam is over (making the code run and solving question 1 and 4) 86 | 87 | ### 4clojure's [28th problem](http://www.4clojure.com/problem/28) 88 | 89 | This problem was an issue for me and I tried a few times and ended up with StackOverflows. Today I tried again, from scratch and I got to the solution almost immediately. The key here was to start small with the end of the function (the `concat`) and gradually add more cases (is the element a collection? Are we in the base case?). So here is my solution: 90 | 91 | ```clojure 92 | (fn my-flatten 93 | [xs] 94 | (if (empty? xs) 95 | '() 96 | (concat (if (coll? (first xs)) 97 | (my-flatten (first xs)) 98 | (list (first xs))) 99 | (my-flatten (rest xs))))) 100 | ``` 101 | 102 | ### Other 4clojure problems 103 | 104 | All in all, I completed 11 4clojure problems today which is much more than I anticipated. I wanted to reach my milestone of 60 problems (my goal is 100 and I have 4 more weeks to get there). 105 | 106 | One interesting problem is [83](http://www.4clojure.com/problem/83). I solved it with a `cond`, that I learned today but this is somewhat brute forcing it: 107 | 108 | ```clojure 109 | (fn half-truth [& args] 110 | (cond 111 | (every? true? args) false 112 | (some true? args) true 113 | :else false)) 114 | ``` 115 | 116 | At least it works. 117 | 118 | However, there's a much smarter way to go about it, as my cohort-mates showed me and it is `#(not= %&)`. So how does this work? We know that `not=` is "not equal" and `%&` is the rest of the arguments. Simply if all the arguments are the same, then it will return false (if they are all true or all false), and in the other case (some true, some false), it will return true. 119 | 120 | ## Takeaways 121 | 122 | While I didn't get the program to fully run today, I still learned a lot about how to structure my code. I don't think I am missing much and will be able to get back to it. 123 | I was also able to complete [11 4clojure challenges](../code/4clojure.md), including a couple that were head-scratchers for me. 124 | 125 | Tomorrow will be Chapter 10 (there is no Chapter 9) and it will be the end of Clojure from the Ground Up. It's been a rewarding experience so far but I am super excited to tackle [Clojure for the Brave and True: Learn the Ultimate Language and Become a Better Programmer](https://braveclojure.com). 126 | 127 | Let's end the day with a tally of what I completed: 128 | 129 | - Chapter 8 of Clojure From the Ground Up 130 | - And two of its questions 131 | - 11 4clojure problems, which means I am 60% done with goal of 100 problems! 132 | -------------------------------------------------------------------------------- /posts/2020-07-01.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 2 Day 3 (10/35) 2 | 3 | ## Expectations 4 | 5 | Today the expectation was to finish Clojure from the Ground Up. There is no more chapter after the debugging one. Usually I read and take notes for half a chapter per day but this one seems not heavy on new knowledge and code so I hope to complete it in one evening. 6 | 7 | If I have more time I hope to make some progress with my 4clojure problems, after all I just got to my 60 problem milestone, and cannot stop here! 8 | 9 | If I get to 100 before the end of ClojureFam I plan on revisiting some earlier problems and attempting to solve them with different (better) approaches. For instance, not use a recursion when it's possible. 10 | 11 | ## What I learned 12 | 13 | ### Debugging in Clojure 14 | 15 | Since I started reading this book, I have felt ill-equipped to debug my code, so what I learned in this chapter has been very interesting. It starts with understanding the error messages and being able to analyze the stack traces as well. 16 | The first example given by the book is: 17 | 18 | ```clojure 19 | (ns scratch.debugging) 20 | 21 | (defn bake 22 | "Bakes a cake for a certain amount of time, returning a cake with a new 23 | :tastiness level." 24 | [pie temp time] 25 | (assoc pie :tastiness 26 | (condp (* temp time) < 27 | 400 :burned 28 | 350 :perfect 29 | 300 :soggy))) 30 | ``` 31 | 32 | And then the function call is: 33 | 34 | ```clojure 35 | user=> (bake {:flavor :blackberry} 375 10.25) 36 | ClassCastException java.lang.Double cannot be cast to clojure.lang.IFn scratch.debugging/bake (debugging.clj:8) 37 | ``` 38 | 39 | Without looking at the stack trace we can see it must have to do with the last argument of the function, time (= 10.25 which is a Double). Then there may be an issue with `condp` because it was expecting a function (`IFn`) and it got a `Double`. This is probably not what [`condp`](https://clojuredocs.org/clojure.core/condp) was expecting. It's expecting a binary predicate (a function that returns true or false). So function first, then two arguments: 40 | 41 | ```clojure 42 | (condp < (* temp time) 43 | 400 :burned 44 | 350 :perfect 45 | 300 :soggy) 46 | ``` 47 | 48 | When we're looking at stack traces like this, for instance when we get a NullPointerException (when a function is expecting a value and receives `nil`), it's a little bit annoying to not be able to understand how that value got there. Usually, in JavaScript, I would put a (conditional) breakpoint before the crash so I could see where this value is coming from. The author offers to use `prn` in a reduce for instance. It's helpful of course, but I would like more. 49 | 50 | It's also interesting that I am getting more and more into types (with TypeScript) and now I am once again looking into a dynamically typed language. You end up relying a lot on types... 51 | 52 | What I should remember though is the spy function he passes to the macro: 53 | 54 | ````clojure 55 | (defn spy 56 | [& args] 57 | (apply prn args) 58 | (last args)) 59 | 60 | (let [segments (->> photos 61 | ; Convert photos to frame dimensions 62 | (map (partial frame mat-width)) 63 | (spy :frames) 64 | ; Convert frames to segments 65 | (mapcat perimeter))] 66 | ``` 67 | ### Interesting 4clojure problem: 63 68 | Given a function f and a sequence s, write a function which returns a map. The keys should be the values of f applied to each item in s. The value at each key should be a vector of corresponding items in the order they appear in s. 69 | 70 | For this problem it is obviously not allowed to use `group-by`. This does look like a reduce so let's start with this. To this reduce we will pass a function, a start value `{}` and the collection. 71 | ```clojure 72 | (fn group-sequence [f coll] 73 | (reduce 74 | (fn [] ()) 75 | {} coll)) 76 | ```` 77 | 78 | We are going to use the result of each element by that function f, so let's compute the result in a let: 79 | 80 | ```clojure 81 | (fn group-sequence [f coll] 82 | (reduce 83 | (fn [m e] (let [result (f e)] 84 | ())) 85 | {} coll)) 86 | ``` 87 | 88 | That function within the reduce needs to append to a vector of existing values after that keyword, so for that we will use `assoc`: 89 | 90 | ```clojure 91 | (fn group-sequence [f coll] 92 | (reduce 93 | (fn [m e] (let [result (f e)] 94 | (assoc m result e))) 95 | {} coll)) 96 | ``` 97 | 98 | However, we are not storing the result, but appending it to the vector, so we use `get` to retrieve the current value: 99 | 100 | ```clojure 101 | (fn group-sequence [f coll] 102 | (reduce 103 | (fn [m e] (let [result (f e)] 104 | (assoc m result (conj (vec (get m result)) e)))) 105 | {} coll)) 106 | ``` 107 | 108 | ## Takeaways 109 | 110 | Today I learned to utilize the stack trace to troubleshoot my issues. While it's I'm welcoming this knowledge I will still look into setting up a debugger potentially (maybe Calva and VS Code can work together?). 111 | 112 | This was the last chapter of the book. All in all it was a great experience and quite easy to understand, take notes from the material. The chapters with long code snippets were harder to navigate as it was much easier to introduce a bug (and the rest relies on earlier code) and get you stuck for the rest of the chapter. 113 | 114 | I also completed another 8 4clojure problems. I'm over 2/3 of my goal, but I do expect the difficulty to be significantly higher when I get to the medium ones, so I'm enjoying this while it lasts. 115 | 116 | Let's end the day with a tally of what I completed: 117 | 118 | - Chapter 10 of Clojure From the Ground Up, which was the last chapter of the book! 119 | - 8 4clojure problems 120 | -------------------------------------------------------------------------------- /posts/2020-07-02.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 2 Day 4 (11/35) 2 | 3 | ## Expectations 4 | 5 | Today I am starting [Clojure for the Brave and True](https://braveclojure.com). My expectation is to skim chapter 1 & 2, and to read chapter 3. I will only take notes about the things that are new to me, or that I feel should be repeated for the sake of me learning them. 6 | 7 | ## What I learned 8 | 9 | ### Datastructures review 10 | 11 | Since I am only writing little tidbits of information I will use bullet points: 12 | 13 | - When using an `if`, the else expression is optional 14 | - `do` groups expressions together, useful if you need to do several things in the then statement of an `if` 15 | - `when` is a like the combination of `if` and `do`, it's taking a binary expression and will evaluate all the other arguments if it is true 16 | - `or` and `and` are interesting in the sense that they will return the respectively the first and the last truthy value 17 | - In Clojure, you bind a name to a value with `def`. As opposed to JavaScript it is encourage to assign and re-assign (a lot less in TypeScript I found out). In Clojure you end up using functions and not modifying the symbols. There is usually not equivalent to reassigning in place in a datastructure. 18 | 19 | #### Summary of types and datastructures 20 | 21 | - numbers 22 | - string 23 | - maps `{}` (similar to hashes or dictionaries in other languages) 24 | - they can be nested 25 | - there are hash-maps and sorted-maps 26 | - `hash-map` creates a map: `(hash-map :a 1 :b 2)` 27 | - can also be used as a function to retrieve a value: `({:a 1} :a)` => `1`, but also `(:a {:a 1})` => `1` 28 | - keywords `:a` 29 | - vectors `[1 2 3]` 30 | - you can also use `get` on them, with the index as the second argument 31 | - lists `'(1 2 3)` 32 | - sets are collections of unique values `#{"hello" 1 :hi}` 33 | - `hash-set` creates a set 34 | - to get a value you can use `contains?`, `get`, or use a keyword as function 35 | 36 | ### Functions 37 | 38 | - Functions that can take a function as an argument, or return a function are higher-order functions. 39 | - It is possible to define multiple functions depending on how many arguments you pass to a function: 40 | 41 | ```clojure 42 | (defn multi-arity 43 | ;; 3-arity arguments and body 44 | ([first-arg second-arg third-arg] 45 | (do-things first-arg second-arg third-arg)) 46 | ;; 2-arity arguments and body 47 | ([first-arg second-arg] 48 | (do-things first-arg second-arg)) 49 | ;; 1-arity arguments and body 50 | ([first-arg] 51 | (do-things first-arg))) 52 | ``` 53 | 54 | It's also a way to define default arguments: 55 | 56 | ```clojure 57 | (defn x-chop 58 | "Describe the kind of chop you're inflicting on someone" 59 | ([name chop-type] 60 | (str "I " chop-type " chop " name "! Take that!")) 61 | ([name] 62 | (x-chop name "karate"))) 63 | ``` 64 | 65 | #### Destructuring 66 | 67 | You can bind names to values within a collection: 68 | 69 | ```clojure 70 | (defn destruct 71 | [[a b & c]] 72 | (println (str "1 " a)) 73 | (println (str "2 " b)) 74 | (println (str "rest " c))) 75 | ``` 76 | 77 | You can also destructure maps: 78 | 79 | ```clojure 80 | (defn destruct-map 81 | [{a :a b :b}] 82 | (println (str "1 " a)) 83 | (println (str "2 " b))) 84 | 85 | => (destruct-map {:a 1 :b 2}) 86 | ``` 87 | 88 | An even shorter way is: 89 | 90 | ```clojure 91 | (defn announce-treasure-location 92 | [{:keys [lat lng]}] 93 | (println (str "Treasure lat: " lat)) 94 | (println (str "Treasure lng: " lng))) 95 | ``` 96 | 97 | Using `:as` lets you retain access to the original map: 98 | 99 | ```clojure 100 | (defn receive-treasure-location 101 | [{:keys [lat lng] :as treasure-location}] 102 | (println (str "Treasure lat: " lat)) 103 | (println (str "Treasure lng: " lng)) 104 | 105 | ;; One would assume that this would put in new coordinates for your ship 106 | (steer-ship! treasure-location)) 107 | ``` 108 | 109 | ### Loops 110 | 111 | I have not covered `loop` yet. 112 | 113 | ```clojure 114 | (loop [i 0] ; initialize i at 0 115 | (printl (str i)) 116 | (if (> i 4) ; here will stop the loop 117 | (println "end") 118 | (recur (int i)))) ; here we increment i and start the loop again 119 | ``` 120 | 121 | Loop has much better performance than a recursive call. 122 | 123 | ### Clojure for the Brave and True Chapter 3 Exercises 124 | 125 | #### 1. Use str, vector, list, hash-map and hash-set 126 | 127 | ````clojure 128 | (str "hello " "world") 129 | (vector '(1 2 3)) 130 | (list 1 2 3) 131 | (hash-map :a 1 :b 2 :c 3) 132 | (hash-set 1 1 2)``` 133 | #### 2. Write a function that takes a number and adds 100 to it 134 | ```clojure 135 | #(+ % 100) 136 | ```` 137 | 138 | #### 3. Write a function, dec-maker, that works exactly like the function inc-maker except with subtraction: 139 | 140 | ```clojure 141 | (defn dec-maker 142 | [n] 143 | #(- % n)) 144 | 145 | (def dec9 (dec-maker 9)) 146 | (dec9 10) 147 | ``` 148 | 149 | #### 4. Write a function, mapset, that works like map except the return value is a set: 150 | 151 | ```clojure 152 | (defn mapset 153 | [f coll] 154 | (set (map f coll))) 155 | ``` 156 | 157 | #### 5. Create a function that’s similar to symmetrize-body-parts except that it has to work with weird space aliens with radial symmetry. Instead of two eyes, arms, legs, and so on, they have five. 158 | 159 | ```clojure 160 | (defn matching-parts 161 | [part] 162 | #{{:name (clojure.string/replace (:name part) #"^left-" "top-") 163 | :size (:size part)} 164 | {:name (clojure.string/replace (:name part) #"^left-" "right-") 165 | :size (:size part)} 166 | {:name (clojure.string/replace (:name part) #"^left-" "bottom-left-") 167 | :size (:size part)} 168 | {:name (clojure.string/replace (:name part) #"^left-" "bottom-right-") 169 | :size (:size part)}}) 170 | 171 | (defn symmetrize-body-parts 172 | "Expects a seq of maps that have a :name and :size" 173 | [asym-body-parts] 174 | (loop [remaining-asym-parts asym-body-parts 175 | final-body-parts []] 176 | (if (empty? remaining-asym-parts) 177 | final-body-parts 178 | (let [[part & remaining] remaining-asym-parts] 179 | (recur remaining 180 | (into final-body-parts 181 | (conj (matching-parts part) part))))))) 182 | ``` 183 | 184 | ## Takeaways 185 | 186 | Overall it was a good revision of what I have learned in Clojure from the Ground Up. The order of things is a little bit different which is refreshing. Some things we didn't cover in CFTGU were covered at the beginning of the chapter (like loops). 187 | 188 | I also did 5 out of 6 of the exercises in this chapter. They were quite easy but that makes sense as it is the fist chapter a beginner would study. 189 | Let's end the day with a tally of what I completed: 190 | 191 | - Chapter 3 of Clojure for the Brave and True 192 | - 5 problems included in the first chapter 193 | -------------------------------------------------------------------------------- /posts/2020-07-04.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 2 Day 6 (13/35) 2 | 3 | ## Expectations 4 | 5 | Little to no expectations, after all it's a holiday! (and I didn't even post this update on time) 6 | 7 | ## What I learned 8 | 9 | ### Learn Datalog Today Chapter 7 10 | 11 | #### Exercise 0: count the number of movies in the database 12 | 13 | ```clojure 14 | [:find (count ?movie) 15 | :where 16 | [?movie :movie/title]] 17 | ``` 18 | 19 | #### Exercise 1: Find the birth date of the oldest person in the database. 20 | 21 | ```clojure 22 | [:find (min ?date) 23 | :where 24 | [_ :person/born ?date]] 25 | ``` 26 | 27 | #### Exercise 2: Given a collection of actors and (the now familiar) ratings data. Find the average rating for each actor. The query should return the actor name and the avg rating. 28 | 29 | ```clojure 30 | [:find ?name (avg ?rating) 31 | :in $ [?name ...] [[?title ?rating]] 32 | :where 33 | [?p :person/name ?name] 34 | [?m :movie/title ?title] 35 | [?m :movie/cast ?p]] 36 | ``` 37 | 38 | ### Chapter 8 39 | 40 | #### Exercise 0: Write a rule [movie-year ?title ?year] where ?title is the title of some movie and ?year is that movies release year. 41 | 42 | ```clojure 43 | [[(movie-year ?title ?year) 44 | [?m :movie/year ?year] 45 | [?m :movie/title ?title]]] 46 | ``` 47 | 48 | #### Exercise 1: Two people are friends if they have worked together in a movie. Write a rule [friends ?p1 ?p2] where p1 and p2 are person entities. Try with a few different ?name inputs to make sure you got it right. There might be some edge cases here. 49 | 50 | ```clojure 51 | [[(friends ?p1 ?p2) 52 | [?m :movie/cast ?p1] 53 | [?m :movie/cast ?p2] 54 | [(not= ?p1 ?p2)]] 55 | [(friends ?p1 ?p2) 56 | [?m :movie/cast ?p1] 57 | [?m :movie/director ?p2]]] 58 | ``` 59 | 60 | ## Takeaways 61 | 62 | I am glad I finished Learn Datalog Today, the last two chapters were more difficult that the previous ones, took longer than expected. 63 | If the \$25 are not claimed, I'll donate the rest to a charity. 64 | -------------------------------------------------------------------------------- /posts/2020-07-05.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 2 Day 7 (14/35) 2 | 3 | ## Expectations 4 | 5 | This is a rest day after the holiday, I expect to do as much as I can of Chapter 4 of Brave Clojure without setting expectations too high. 6 | 7 | ## What I learned 8 | 9 | ### Programming by Abstractions 10 | 11 | **Programming by abstractions** means that a certain function is not tailored to a specific data structure. It is about the operation, not about the underlying datastructure. 12 | 13 | > For example, the **battery** abstraction includes the operation “connect a conducting medium to its anode and cathode,” and the operation’s output is **electrical current**. It doesn’t matter if the battery is made out of lithium or out of potatoes. It’s a battery as long as it responds to the set of operations that define **battery**. 14 | 15 | In Clojure, if `cons`, `first` and `rest` work on a datastructure then it's a seq and any seq funcion will work on it. Potentially it is because these functions are implemented with `cons`, `first` and `rest`. 16 | 17 | So I tried to re-implement `map` in terms of these three functions: 18 | 19 | ```clojure 20 | (defn my-map 21 | [f coll] 22 | (if (nil? coll) 23 | nil 24 | (cons (f (first coll) (map (rest coll)))))) 25 | ``` 26 | 27 | After testing it out, I looked up the code, and indeed, `map` is made with these three functions: https://github.com/clojure/clojure/blob/clojure-1.10.1/src/clj/clojure/core.clj#L2727 28 | 29 | ### More about `seq functions` 30 | 31 | #### `map` 32 | 33 | - It is possible to pass more than one sequence to `map`. 34 | - You can also pass it a collection of functions, here is a great example: 35 | 36 | ```clojure 37 | (def sum #(reduce + %)) 38 | (def avg #(/ (sum %) (count %))) 39 | (defn stats 40 | [numbers] 41 | (map #(% numbers) [sum count avg])) 42 | 43 | (stats [3 4 10]) 44 | ; => (17 3 17/3) 45 | 46 | (stats [80 1 44 13 6]) 47 | ; => (144 5 144/5) 48 | ``` 49 | 50 | - Or to retrieve the the value associated with a keyword. Again here is an example from Brave Clojure: 51 | 52 | ```clojure 53 | (def identities 54 | [{:alias "Batman" :real "Bruce Wayne"} 55 | {:alias "Spider-Man" :real "Peter Parker"} 56 | {:alias "Santa" :real "Your mom"} 57 | {:alias "Easter Bunny" :real "Your dad"}]) 58 | 59 | (map :real identities) 60 | ; => ("Bruce Wayne" "Peter Parker" "Your mom" "Your dad") 61 | ``` 62 | 63 | #### `reduce` 64 | 65 | - It's possible to use `reduce` to transform a map's value (same keys but updated values): 66 | 67 | ````clojure 68 | (reduce (fn [new-map [key val]] 69 | (assoc new-map key (inc val))) 70 | {} 71 | {:max 30 :min 10}) 72 | ; => {:max 31, :min 11}``` 73 | - You can also use `reduce` to filter out some values in a map: 74 | ```clojure 75 | (reduce (fn [new-map [key val]] 76 | (if (> val 4) 77 | (assoc new-map key val) 78 | new-map)) 79 | {} 80 | {:human 4.1 81 | :critter 3.9}) 82 | ; => {:human 4.1} 83 | ```` 84 | 85 | #### `take` and `drop` 86 | 87 | - Both take a sequence and a number and will return either the first n elements of the sequence (for the former), or everything but the first n elements (for the latter). 88 | - There is also `take-while` and `drop-while` that will take a predicate function instead of the number. However, in this case I would normally use `filter`. The advantage of `take-while` and `drop-while` is that it doesn't process all your data. It stops when the predicate is not satisfied anymore. 89 | 90 | #### `filter` and `some` 91 | 92 | You can get some to return the actual value of a function if you transform the predicate function by wrapping it in an `and` like this: `(some #(and (> (:critter %) 3) %) food-journal)`. 93 | 94 | #### `sort` and `sort-by` 95 | 96 | `sort-by` takes a function to sort, so you could use `count` for instance 97 | 98 | ## Takeaways 99 | 100 | Today I read and took notes for a little more than half of the 4th Chapter of Clojure for the Brave and True. It goes further than Clojure for the Ground Up on the same topics. Going through it now makes complete sense as I have the basics down and I can pick up why things are the way they are, and I hope, why Clojure makes sense. 101 | -------------------------------------------------------------------------------- /posts/2020-07-06.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week X Day Y (15/35) 2 | 3 | ## Expectations 4 | 5 | Today is the beginning of week 3 of ClojureFam (how time flies...) 6 | 7 | ## What I learned 8 | 9 | ### Lazy Seqs 10 | 11 | `map` is an example of a lazy function. It returns almost instantly and only when the data is accessed, does it apply the function it took as an argument. Every **unrealized** element has the **recipe** for realizing each element. Once an element is realized, it doesn't need to be realized again to be accessed. 12 | 13 | However, Clojure chunks its unrealized elements when you try to access them. So if you try to access the first n elements of sequence, it will realize a bunch more as well. 14 | 15 | ### Infinite seqs 16 | 17 | To create infinite sequences, we use `repeat` and `repeatedly` (the latter taking a function). 18 | 19 | ### The Collection Abstraction 20 | 21 | #### `into` 22 | 23 | `into` will turn the result of a seq function (it returns a sequence, a list) into another data structure. So for instance it can turn the result of a map taking a set back into a set. 24 | 25 | ```clojure 26 | (into {} (map identity {:sunlight-reaction "Glitter!"})) 27 | ; => {:sunlight-reaction "Glitter!"} 28 | ``` 29 | 30 | One more thing to note is that the first collection doesn't have to be empty. 31 | 32 | #### `conj` 33 | 34 | `conj` takes a vector, and one or several new elements to append at the end. It's very similar to `into` but takes rest parameter instead. 35 | 36 | ### Function Functions 37 | 38 | One thing I've wanted to do since I started learning clojure is taking a sequence and pass it to a function that takes rest arguments, like `conj` we just looked into. And the way to do it is with... 39 | 40 | #### `apply` 41 | 42 | `apply` does just that: `(apply max [0 1 2])` => `2` 43 | 44 | #### `partial` 45 | 46 | `partial` I am already fine with, it takes a function and arguments then returns a functions which can take arguments, and that new function will take the new arguments alongside the originally supplied ones: 47 | 48 | ```clojure 49 | (def add10 (partial + 10)) 50 | (add10 3) 51 | ; => 13 52 | (add10 5) 53 | ; => 15 54 | ``` 55 | 56 | #### `complement` 57 | 58 | `complement` is new to me. It simply inverts a predicate function so for instance you could do `(def non-nil? (complement nil?))`. 59 | 60 | ### Brave Clojure Chapter 4 Exercises 61 | 62 | #### Reloading the REPL 63 | 64 | - Load core.clj with `(ns fwpd.core)`, reload with `(use 'fwpd.core :reload)` 65 | 66 | #### 1. Turn the result of your glitter filter into a list of names. 67 | 68 | ```clojure 69 | (defn extract-names 70 | [records] 71 | (map #(:name %) records)) 72 | ``` 73 | 74 | #### 2. Write a function, append, which will append a new suspect to your list of suspects. 75 | 76 | ```clojure 77 | (defn append 78 | [records new-suspect] 79 | (conj records new-suspect)) 80 | ``` 81 | 82 | #### 3. Write a function, validate, which will check that :name and :glitter-index are present when you append. The validate function should accept two arguments: a map of keywords to validating functions, similar to conversions, and the record to be validated. 83 | 84 | ```clojure 85 | (defn validate 86 | [keyword-to-validating-function record] 87 | (apply = true (map (fn [key] ((key keyword-to-validating-function) (key record))) (keys keyword-to-validating-function)))) 88 | ``` 89 | 90 | #### 4. Write a function that will take your list of maps and convert it back to a CSV string. You’ll need to use the clojure.string/join function. 91 | 92 | ```clojure 93 | (defn convert-to-csv 94 | [records] 95 | (clojure.string/join "\n" (map (fn [record] (clojure.string/join "," (map str (vals record)))) records))) 96 | ``` 97 | 98 | ## Takeaways 99 | 100 | The content of the Chapter 4 of Clojure for the Brave and True were not new in itself (barely any new function for instance), but it went much deeper into why Clojure behaves the way it is, and why the core functions are what they are. I may still not be getting Clojure itself (why Clojure and not JavaScript for instance), but at least I am starting to understand some of the abstractions. Programming by abstractions definitely has a big appeal, forget about the nitty gritty of the implementation, and focus on what you want to achieve (for instance with the sequence abstraction). 101 | 102 | Another takeaway that from today were the exercises: while the first two were super simple, the last two, while not being too complicated were challenging and interesting in the sense that they were mirroring things that I often end up scripting in JavaScript. More precisely string parsing and manipulating. For instance I recently started working on a tool that extracts specific items from a Roam Research backup. This is definitely something that I am starting to understand how to do in Clojure. 103 | -------------------------------------------------------------------------------- /posts/2020-07-07.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 3 Day 2 (16/35) 2 | 3 | ## Expectations 4 | 5 | The expectation today was cover chapter 5 of Brave Clojure. My cohort-mate and were also on the final stretch for [our collaborative Athens issue](https://github.com/athensresearch/athens/issues/209) so I wanted to cover some of that as well. 6 | 7 | ## What I learned 8 | 9 | ### What makes a pure function 10 | 11 | - A pure function always returns the same result given the same argument(s). This is called **referential transparency**. 12 | - A pure function cannot cause any side effects (changes that are observable outside the function itself). 13 | With pure function you pass the result of a function as argument to another. It's called **function composition**. Functional programming encourages you to build more complex functions by combining simpler functions. 14 | 15 | ### Reminder on how to use arity overloading to do the starting case (here is a sum): 16 | 17 | ```clojure 18 | (defn sum 19 | ([vals] (sum vals 0)) 20 | ([vals accumulating-total] 21 | (if (empty? vals) 22 | accumulating-total 23 | (sum (rest vals) (+ (first vals) accumulating-total))))) 24 | ``` 25 | 26 | A more efficient is to do this with `recur`: 27 | 28 | ```clojure 29 | (defn sum 30 | ([vals] 31 | (sum vals 0)) 32 | ([vals accumulating-total] 33 | (if (empty? vals) 34 | accumulating-total 35 | (recur (rest vals) (+ (first vals) accumulating-total))))) 36 | ``` 37 | 38 | ### `comp` 39 | 40 | `comp` takes function and returns a new anonymous function that is the function composition of them. `((comp inc *) 2 3)`, first it multiplies and then takes the result and increases it. 41 | 42 | Here is another example to access nested maps: 43 | 44 | ```clojure 45 | def character 46 | {:name "Smooches McCutes" 47 | :attributes {:intelligence 10 48 | :strength 4 49 | :dexterity 5}}) 50 | (def c-int (comp :intelligence :attributes)) 51 | (def c-str (comp :strength :attributes)) 52 | (def c-dex (comp :dexterity :attributes)) 53 | 54 | (c-int character) 55 | ; => 10 56 | 57 | (c-str character) 58 | ; => 4 59 | 60 | (c-dex character) 61 | ; => 5 62 | ``` 63 | 64 | ### `memoize` 65 | 66 | `memoize` saves the arguments and the return of a function, so when you evaluate the function with the same arguments again the result is returned immediately. 67 | 68 | ```clojure 69 | (def memo-sleepy-identity (memoize sleepy-identity)) 70 | (memo-sleepy-identity "Mr. Fantastico") 71 | ; => "Mr. Fantastico" after 1 second 72 | 73 | (memo-sleepy-identity "Mr. Fantastico") 74 | ; => "Mr. Fantastico" immediately 75 | ``` 76 | 77 | ### Brave Clojure Chapter 5 Exercises 78 | 79 | #### Exercise 1: You used (comp :intelligence :attributes) to create a function that returns a character’s intelligence. Create a new function, attr, that you can call like (attr :intelligence) and that does the same thing. 80 | 81 | ```clojure 82 | (defn attr 83 | [attribute] 84 | (comp attribute :attributes)) 85 | ``` 86 | 87 | #### Exercise 2: Implement the comp function. 88 | 89 | ```clojure 90 | (defn my-comp 91 | [& functions] 92 | (fn [& args] 93 | (reduce (fn [arg f] (f arg)) (apply (last functions) args) (reverse functions)))) 94 | ``` 95 | 96 | This one was quite challenging, and I'm happy I found a solution. It's for sure different from the ones already posted online. 97 | 98 | #### Exercise 3: Implement the assoc-in function. Hint: use the assoc function and define its parameters as [m [k & ks] v]. 99 | 100 | ```clojure 101 | (defn my-assoc-in 102 | [m [k & ks] val] 103 | (if (nil? ks) (assoc m k val) (assoc m k (my-assoc-in (get m k {}) ks val)))) 104 | ``` 105 | 106 | #### Exercise 4: Look up and use the update-in function. 107 | 108 | ```clojure 109 | user=> 110 | (def foo {:user {:bar 1}}) 111 | #'user/foo 112 | user=> 113 | (update-in foo [:user :bar] inc) 114 | {:user {:bar 2}} 115 | ``` 116 | 117 | #### Exercise 5: Implement update-in. 118 | 119 | ```clojure 120 | (def foo {:user {:bar 1}}) 121 | 122 | (defn my-update-in 123 | [m [k & ks] f] 124 | (if (nil? ks) (do (prn "hello") (assoc m k (f (get m k 1)))) (assoc m k (my-assoc-in (get m k {}) ks f)))) 125 | 126 | (my-update-in foo [:user :bar] inc) 127 | ``` 128 | 129 | This one doesn't work just yet, it's almost there but instead of evaluating the function with arguments it just returns the function itself. 130 | 131 | ### Continuing investigation on issue 132 | 133 | Today I am looking at the unindent problem, part of the same issue. When on a page, the user cannot unindent a node that is already at room level. However, when the user has "entered" a node (that's when you click on the bullet and zoom into a node that becomes a page), that's when it is possible to unindent it (it goes back to the page, which makes sense but is counterintuitive for the user). 134 | 135 | I originally thought it was the `enter` function that was responsible for getting into the node, but this is actually for pressing... Enter. 136 | 137 | Our next lead was `:current-route` which was closer. Eventually we found a way to pass the navigated page down to the unindent event which we can then compare to the parent node of the current node and figure out if we can indent or not. 138 | 139 | ```clojure 140 | (defn unindent 141 | [uid context-root] 142 | (let [parent (db/get-parent [:block/uid uid]) 143 | grandpa (db/get-parent (:db/id parent)) 144 | new-block {:block/uid uid :block/order (inc (:block/order parent))} 145 | reindex-grandpa (->> (inc-after (:db/id grandpa) (:block/order parent)) 146 | (concat [new-block]))] 147 | (if (= (:block/uid parent) context-root) 148 | {} 149 | (when (and parent grandpa) 150 | {:transact! [[:db/retract (:db/id parent) :block/children [:block/uid uid]] 151 | {:db/id (:db/id grandpa) :block/children reindex-grandpa}]})))) 152 | 153 | 154 | (reg-event-fx 155 | :unindent 156 | (fn [{rfdb :db} [_ uid]] ;; Pass in the reframe db as a cofx to the :unindent event handler 157 | (unindent uid (get-in rfdb [:current-route :path-params :id])))) 158 | ``` 159 | 160 | ## Takeaways 161 | 162 | I'm glad the brave clojure chapter was lighter than the others, so I was able to spend more time on troubleshooting our issue. We now even have [an open PR](https://github.com/athensresearch/athens/pull/228) for it! 163 | 164 | The exercises were also challenging and I am glad I completed them. I got to compare my solutions with others posted on GitHub and mine are different, no one came up with super smart solutions, which is reassuring. 165 | 166 | Tomorrow I will spend some time looking into the existing issues and see if I can pick one up by myself. 167 | 168 | Let's end the day with a tally of what I completed: 169 | 170 | - Completed Chapter 5 of Clojure for the Brave and True 171 | - Also did the 5 exercises in that same chapter 172 | - FInally solved the last problems in our first collaborative issue in Athens, and opened the PR 173 | -------------------------------------------------------------------------------- /posts/2020-07-08.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 3 Day 3 (17/35) 2 | 3 | ## Expectations 4 | 5 | Yesterday I finished Chapter 5 of Brave Clojure. I quickly skimmed through Chapter 6 and it's much shorter than the others with no exercises. The expectation for today is therefore to read and take notes on Chapter 5 and then look into [the second issue](https://github.com/athensresearch/athens/issues/126) that me and my cohort-mates are tackling together. This issue has a much larger scope that the one we merged today, but is also very interesting. 6 | 7 | ## What I learned 8 | 9 | ### Namespaces 10 | 11 | - You can refer to the current namespace with `*ns*`. 12 | - You can get the name of a namespace with `(ns-name *ns*)`. 13 | - When starting the REPL, you are in the `user` namespace. 14 | - When you use `def` you are **interning** a var. 15 | - You can get a map of all the interned vars by doing `(ns-interns *ns*)`, and then `(get (ns-interns *ns*) 'great-books)` will get a specific var. 16 | - `(ns-map *ns*)` will return the full map of the namespace. 17 | - `#'user/fun` is the **reader form** of a var. 18 | - `deref` and the **reader form** and you can get the object they point to. 19 | 20 | #### Creating and switching to namespaces 21 | 22 | - `create-ns` like `(create-ns 'hello.world)` 23 | - `in-ns` will create (if it doesn't exist) and switch to that namespace 24 | 25 | #### Accessing symbols from other namespaces 26 | 27 | - You can use symbols from other namespaces by using their fully qualified symbol, like `clojure.string/split`. 28 | - Another way is to use `refer`: 29 | 30 | ```clojure 31 | (clojure.core/refer 'other.namespace) ; contains symbol sym 32 | sym 33 | ``` 34 | 35 | In effect this is like merging the current namespace with another one. I expect collisions might be on the menu. 36 | 37 | It is also possible to use filters with it such as `:only`, `:exclude` and `:rename` 38 | - `defn-` let's you declare private functions only accessible within the same namespace. 39 | - finally there is `alias`, which lets you shorten a namespace name like so `(clojure.core/alias 'taxonomy 'cheese.taxonomy)` 40 | 41 | ### Real project organization 42 | Even though I have already covered this in [Chapter 7](https://github.com/alaq/learning-clojure-in-public/blob/master/posts/2020-06-27.md) of Clojure from the Ground Up it is always good to review and especially find out more about namespaces in this context. 43 | 44 | When looking at a namespace, like `firstpart.secondpart`, `firstpart` is the directory and the full thing is the namespace name. 45 | 46 | #### `require` 47 | `require` finds and evaluates the file matching the namespace name. Clojure expects that file to declare a namespace matching its path. It will also alias a namespace with `:as` like `(require '[the-divine-cheese-code.visualization.svg :as svg])`. This is equivalent to: 48 | 49 | ```clojure 50 | (require 'the-divine-cheese-code.visualization.svg) 51 | (alias 'svg 'the-divine-cheese-code.visualization.svg) 52 | ``` 53 | 54 | Then there is `use` that does what `require` and then `refer` does so: 55 | 56 | ```clojure 57 | (require 'the-divine-cheese-code.visualization.svg) 58 | (refer 'the-divine-cheese-code.visualization.svg) 59 | (alias 'svg 'the-divine-cheese-code.visualization.svg) 60 | ``` 61 | 62 | can be replaced by this: 63 | 64 | ```clojure 65 | (use '[the-divine-cheese-code.visualization.svg :as svg]) 66 | (= svg/points points) 67 | ; => true 68 | 69 | (= svg/latlng->point latlng->point) 70 | ; => true 71 | ``` 72 | 73 | #### The ns Macro 74 | 75 | This is the one that is use in source code files. 76 | 77 | - `ns` will refer the `clojure.core` namespace by default 78 | - this two examples are equivalent: 79 | 80 | ```clojure 81 | (ns the-divine-cheese-code.core 82 | (:refer-clojure :exclude [println])) 83 | 84 | (in-ns 'the-divine-cheese-code.core) 85 | (refer 'clojure.core :exclude ['println]) 86 | ``` 87 | 88 | - There is no need to quote the symbols with `ns`. 89 | - The other references are the following 90 | - `(:refer-clojure)` 91 | - `(:require)`, works a lot like `require`, can also use `:as` to alias 92 | - `(:use)` 93 | - `(:import)` 94 | - `(:load)` 95 | - `(:gen-class` 96 | 97 | ## Researching our second issue 98 | 99 | Athena is the search bar used in Athens. The [issue](https://github.com/athensresearch/athens/issues/126) that we are taking is covering a few things: 100 | 101 | - Up and Down to go through the list, and hover over elements. 102 | - having only one element be highlighted at the same time. We're currently leaning towards highlighting items on hover and also on keyboard movements. 103 | - figuring out why the search bar is not working on non-Chromium browsers 104 | Unfortunately I didn't go further than just running these different scenarios on my local version of Athens. 105 | 106 | ## Takeaways 107 | 108 | I did a good review today: I feel like I have all the tools I need to start organizing projects. I looked into how namespaces organize maps between symbols and vars, and that vars are references to Clojure objects. `def` stores an object and updates the current namespace with a map between a symbol and a var that points to the object. I also covered the `ns` macro but will surely have to refer to this chapter again in the future. 109 | 110 | It was also interesting to do some 4clojure problems again (I had not done any since July 1st!). The functions I'm using are slightly more varied (read more tailored to the occasion), which is a good sign. 111 | 112 | Let's end the day with a tally of what I completed: 113 | 114 | - Chapter 6 of Clojure for the Brave and True (there was no exercise this time) 115 | - 4 4clojure problems 116 | - And an initial overview of our next collaborative issue in Athens 117 | -------------------------------------------------------------------------------- /posts/2020-07-09.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 3 Day 4 (18/35) 2 | 3 | ## What I learned 4 | 5 | ### Evaluation in Clojure 6 | 7 | You can send your code straight to the evaluator with `eval`. 8 | 9 | Clojure is homoiconic: it represents abstract syntax trees using lists, and you write textual representations of lists when you write Clojure code. 10 | 11 | Clojure can parse a string into potentially valid Clojure code like `(read-string "(+ 1 2))"`, but then it needs to be passed to `eval` to be evaluated because reading and evaluating are independent of each other. `read-string` doesn't always return a one to one match, for instance: 12 | 13 | ```clojure 14 | (read-string "'(a b c)") 15 | ; => (quote (a b c)) 16 | ``` 17 | 18 | ### Macros 19 | 20 | Macros are executed between the reader and the evaluator—so they can manipulate the data structures that the reader spits out and transform with those data structures before passing them to the evaluator. So for example: 21 | 22 | ```clojure 23 | (defmacro ignore-last-operand 24 | [function-call] 25 | (butlast function-call)) 26 | 27 | (ignore-last-operand (+ 1 2 10)) 28 | ; => 3 29 | 30 | ;; This will not print anything 31 | (ignore-last-operand (+ 1 2 (println "look at me!!!"))) 32 | ; => 3 33 | ``` 34 | 35 | The data structure returned by a function is **not** evaluated, but the data structure returned by a macro **is**. 36 | 37 | `macroexpand` lets you see what datastructure is returned by the macro: 38 | 39 | ```clojure 40 | (macroexpand '(ignore-last-operand (+ 1 2 10))) 41 | ; => (+ 1 2) 42 | ``` 43 | 44 | ### Threading 45 | 46 | You can reverse the order in which function calls are written with `->`: 47 | 48 | ```clojure 49 | (defn read-resource 50 | "Read a resource into a string" 51 | [path] 52 | (read-string (slurp (clojure.java.io/resource path)))) 53 | 54 | ; is equivalent to 55 | 56 | (defn read-resource 57 | [path] 58 | (-> path 59 | clojure.java.io/resource 60 | slurp 61 | read-string)) 62 | ``` 63 | 64 | ### Brave Clojure Chapter 7 Exercises 65 | 66 | #### Exercise 1. Use the list function, quoting, and read-string to create a list that, when evaluated, prints your first name and your favorite sci-fi movie. 67 | 68 | ```clojure 69 | (eval (list (read-string "println") '(str "Adrien " "Battlestar Galactica"))) 70 | ``` 71 | 72 | #### Exercise 2. Create an infix function that takes a list like (1 + 3 \* 4 - 5) and transforms it into the lists that Clojure needs in order to correctly evaluate the expression using operator precedence rules. 73 | 74 | This was similar to [4Clojure problem 135](http://www.4clojure.com/problem/135), the only difference is it is taking a list and not variable arity: 75 | 76 | ```clojure 77 | (defn infix 78 | [xs] 79 | (reduce 80 | (fn [acc e] 81 | (if (fn? e) 82 | (partial e acc) 83 | (acc e))) 84 | (partial + 0) xs)) 85 | ``` 86 | 87 | ### Reagent 88 | 89 | After spending all this time reading introductory Clojure books I have decided it was time to start tackling libraries that are used by the Athens project. `re-frame` seems to be the most important piece here, and it is based on `reagent` which is "a minimalistic interface between ClojureScript and React". 90 | 91 | A Reagent component is written up like this: 92 | 93 | ```clojure 94 | (defn simple-component [] 95 | [:div 96 | [:p "I am a component!"] 97 | [:p.someclass 98 | "I have " [:strong "bold"] 99 | [:span {:style {:color "red"}} " and red "] "text."]]) 100 | ``` 101 | 102 | Notice how it looks like a normal function call but it's using brackets instead. Then the simple component can be reused simply by calling it with `[simple-component]`. 103 | 104 | A component takes arguments like a function: 105 | 106 | ```clojure 107 | (defn hello-component [name] 108 | [:p "Hello, " name "!"]) 109 | 110 | (defn say-hello [] 111 | [hello-component "world"]) 112 | ``` 113 | 114 | In React, it is very common place to have data in an array and use map over it to display a list, for example. In Reagent it seems a `for` loop is used: 115 | 116 | ```clojure 117 | (defn lister [items] 118 | [:ul 119 | (for [item items] 120 | ^{:key item} [:li "Item " item])]) 121 | 122 | (defn lister-user [] 123 | [:div 124 | "Here is a list:" 125 | [lister (range 3)]]) 126 | ``` 127 | 128 | Here `^{:key item}` is used like in React to identify the element. 129 | 130 | #### State in Reagent 131 | 132 | The simplest way is to use Reagent's atoms, `r/atoms`. Every component that uses an atom will be re-rendered when it gets updated. 133 | 134 | ```clojure 135 | (ns example 136 | (:require [reagent.core :as r])) 137 | (def click-count (r/atom 0)) 138 | 139 | (defn counting-component [] 140 | [:div 141 | "The atom " [:code "click-count"] " has value: " 142 | @click-count ". " 143 | [:input {:type "button" :value "Click me!" 144 | :on-click #(swap! click-count inc)}]]) 145 | 146 | ;; Or even within a component 147 | (defn timer-component [] 148 | (let [seconds-elapsed (r/atom 0)] 149 | (fn [] 150 | (js/setTimeout #(swap! seconds-elapsed inc) 1000) 151 | [:div 152 | "Seconds Elapsed: " @seconds-elapsed]))) 153 | ``` 154 | 155 | Then you can pass the atom around for other components to benefit from the state: 156 | 157 | ```clojure 158 | (ns example 159 | (:require [reagent.core :as r])) 160 | (defn atom-input [value] 161 | [:input {:type "text" 162 | :value @value 163 | :on-change #(reset! value (-> % .-target .-value))}]) 164 | 165 | (defn shared-state [] 166 | (let [val (r/atom "foo")] 167 | (fn [] 168 | [:div 169 | [:p "The value is now: " @val] 170 | [:p "Change it here: " [atom-input val]]]))) 171 | ``` 172 | 173 | #### Attaching the React app to a dom node 174 | 175 | You can (and should!) attach the component to a node in your html and you can do so like this: 176 | 177 | ```clojure 178 | (ns example 179 | (:require [reagent.core :as r])) 180 | (defn simple-component [] 181 | [:div 182 | [:p "I am a component!"] 183 | [:p.someclass 184 | "I have " [:strong "bold"] 185 | [:span {:style {:color "red"}} " and red "] "text."]]) 186 | 187 | (defn render-simple [] 188 | (rdom/render 189 | [simple-component] 190 | (.-body js/document))) 191 | ``` 192 | 193 | #### Other things to note about Reagent 194 | 195 | ## Takeaways 196 | 197 | Today I learned about Clojure's evaluation process. I saw how the reader first transforms the text into data structures, then the macroexpander transforms a custom syntax into valid data structures and finally, those data structures are sent to the evaluator. The evaluator processes the data structures depending on their type. 198 | 199 | Today I also got to read the introduction to Reagent, the interface between React and ClojureScript. It does seem quite simple and seems to remove a lot of the overhead you meet in vanilla React. For instance, while going through the example I didn't end up using any lifecycle methods, or even things like `setState`. I like using hooks in React but I am not sure I need to use them in this case. One of my next steps is probably going to be setting up a minimal Reagent application. 200 | 201 | Let's end the day with a tally of what I completed: 202 | 203 | - Chapter 7 of Clojure for the Brave and True 204 | - The complete introduction to Reagent, the minimalistic React for ClojureScript 205 | - 2 4clojure problems 206 | -------------------------------------------------------------------------------- /posts/2020-07-10.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 3 Day 5 (19/35) 2 | 3 | ## Expectations 4 | 5 | It's the end of the week and I have to pace myself. The next chapter in Clojure for the Brave and True is about macros, which I don't want to spend too much time on it. 6 | 7 | I also got stomped on the pascal triangle problem from 4clojure yesterday so one of my goals today is to solve it, my own solution to it. 8 | 9 | ## What I learned 10 | 11 | ### Macros 12 | 13 | A macro is created and used like this: 14 | 15 | ````clojure 16 | (defmacro infix 17 | "Use this macro when you pine for the notation of your childhood" 18 | [infixed] 19 | (list (second infixed) (first infixed) (last infixed))) 20 | 21 | (infix (1 + 1)) 22 | ; => 2``` 23 | The key difference between the macro and a function is that the function arguments are fully evaluated before they're passed to the function. Macro arguments are unevaluated data. When writing a macro, you will most likely have to quote symbols to avoid having them evaluated. 24 | ### Quoting 25 | You use quoting to obtain an evaluated symbol. `(quote (+ 1 2))` will return `(+ 1 2)`. Quoting a symbol returns the symbol regardless if it has values or not. 26 | 27 | Syntax quoting is done by prepending ```. It will always return the namespace on top of the symbol itself. Here is the difference between quoting and syntax quoting: 28 | ```clojure 29 | '(+ 1 2) 30 | ; => (+ 1 2) 31 | 32 | `(+ 1 2) 33 | ; => (clojure.core/+ 1 2)``` 34 | With syntax quoting you can unquote things, so you can do: 35 | ```clojure 36 | `(+ 1 ~(inc 1)) 37 | ; => (clojure.core/+ 1 2) 38 | ```` 39 | 40 | So after the tilde, the expression is evaluated and replaced by 2. In a way it's similar to string interpolation (string interpolation is done like this `Hello #{name}`). The same way, you prepend a tilde and it evaluates in the list. Here are two equivalent ways to do the same thing: 41 | 42 | ```clojure 43 | (list '+ 1 (inc 1)) 44 | ; => (+ 1 2) 45 | 46 | `(+ 1 ~(inc 1)) 47 | ; => (clojure.core/+ 1 2) 48 | ``` 49 | 50 | ### Things to watch out for with macros 51 | 52 | - variable capture happens when the macro hijacks the symbol with a let. 53 | - double evaluation is when a form passed to a macro gets evaluated twice 54 | 55 | ### Pascal's triangle 4clojure problem 56 | 57 | This one was an epic problem to solve. Took me some time, and sweat. In the end the iterate solution was much easier: it runs a function over and over on the result of the previous run. Then you can just keep the last element in the vector. So to do this, first I wrote the next row function which takes the previous one, sums the elements in pair (I discovered that `map` can take multiple collections and run the a function f with the nth element from each collection -- which is great!), and then we just tack a `1` at the beginning and one at the end too. 58 | 59 | Now we have the list of rows (it goes on forever so we cap it with `take` -- at `n-1`), Since with iterate we have to start somewhere we start with [1 1] (because if we use `rest` and `butlast` we end up with empty collections to sum with one another). 60 | 61 | ```clojure 62 | (fn pascal [n] 63 | (if (= n 1) 64 | [1] 65 | (last (take (dec n) (iterate (fn [xs] 66 | (concat 67 | (conj (map + (butlast xs) (rest xs)) 1) 68 | '(1))) 69 | [1 1]))))) 70 | ``` 71 | 72 | ## Takeaways 73 | 74 | The chapter on macros wasn't too useful to me at this point. I am knowingly pushing this back to a later time. I did read most of it and will be getting back to it but after ClojureFam. 75 | 76 | I am happy about completing the pascal triangle problem though. In the end it wasn't that complicated. I just needed to find a way to tackle this. I first started with a recursion, which didn't prove easy after all and `iterate` proved to be a must better choice. 77 | 78 | Let's end the day with a tally of what I completed: 79 | 80 | - Chapter 8 of Clojure for the Brave and True 81 | - One 4clojure problems (the Pascal triangle problem!) 82 | -------------------------------------------------------------------------------- /posts/2020-07-11.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 3 Day 6 (20/35) 2 | 3 | ## Expectations 4 | 5 | I am mostly taking it easy today, it's the weekend after all, so mostly some reading of chapter 9 of Brave Clojure. 6 | 7 | ## What I learned 8 | 9 | ### Concurrency 10 | 11 | Concurrency refers to managing more than one task at the same time. It is possible to tell Clojure to do a task concurrently by placing it on a JVM thread. 12 | 13 | #### Futures 14 | 15 | ```clojure 16 | (future (Thread/sleep 4000) 17 | (println "I'll print after 4 seconds")) 18 | (println "I'll print immediately") 19 | ``` 20 | 21 | As seen before in Clojure from the Ground Up, `future` puts the computation in a different thread, which leaves the main thread available to process something else. 22 | 23 | The `future` function returns a reference value that you can use to request the result. This process is called dereferencing. You can do this with the `deref` function or `@` before the variable. 24 | 25 | ```clojure 26 | (let [result (future (println "this prints once") 27 | (+ 1 1))] 28 | (println "deref: " (deref result)) 29 | (println "@: " @result)); => "this prints once"; => deref: 2; => @: 2 30 | ``` 31 | 32 | Dereferencing will block if the future hasn't resolved the value just yet. 33 | 34 | Something new (that I didn't know yet) is that you can place a limit on how long to wait for a future. 35 | 36 | ```clojure 37 | (deref (future (Thread/sleep 1000) 0) 10 5) 38 | ; => 5 39 | ``` 40 | 41 | This code tells deref to return the value 5 if the future doesn’t return a value within 10 milliseconds. 42 | 43 | `realized?` is a function that tells you if the future is done running. 44 | 45 | ### Delays 46 | 47 | **Delays** allow you to define a task without having to execute it or require the result immediately. You can create a delay using delay: 48 | 49 | ```(def jackson-5-delay 50 | (delay (let [message "Just call my name and I'll be there"] 51 | (println "First deref:" message) 52 | message))) 53 | ``` 54 | 55 | To get the value you can `deref` but also `force` which returns it earlier. 56 | 57 | ### 4Clojure problem 95: to tree or not to tree? 58 | 59 | In this case, if something is nil, it's okay, if a collection has three items it's okay, if any of its second and third element are also satisfying `tree?` it's okay as well. Which gives us the following solution: 60 | 61 | ```clojure 62 | (fn tree? [xs] 63 | (cond 64 | (or (seq? xs) (vector? xs)) (and (= 3 (count xs)) (tree? (nth xs 1)) (tree? (nth xs 2))) 65 | (nil? xs) true 66 | :else false)) 67 | ``` 68 | 69 | ## Takeaways 70 | 71 | Let's end this (rest) day with a tally of what I completed: 72 | 73 | - First half of Chapter 9 of Clojure for the Brave and True 74 | - 1 4clojure problems 75 | -------------------------------------------------------------------------------- /posts/2020-07-12.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 3 Day 7 (21/35) 2 | 3 | ## Expectations 4 | 5 | Yesterday I started reading chapter 9 of Clojure for the Brave and True, and the expectation for today is to complete it and its exercises. 6 | 7 | ## What I learned 8 | 9 | ### Promises 10 | 11 | Promises allow you to express that you expect a result without having to define the task that should produce it or when that task should run. You create promises using promise and deliver a result to them using deliver. You obtain the result by dereferencing: 12 | 13 | ```clojure 14 | (def my-promise (promise)) 15 | (deliver my-promise (+ 1 2)) 16 | @my-promise 17 | ; => 3 18 | ``` 19 | 20 | If a promise never returns a value it will block the thread forever so it might be good to make it time out after an amount of milliseconds, like so: 21 | 22 | ```clojure 23 | (let [p (promise)] 24 | (deref p 100 "timed out")) 25 | ``` 26 | 27 | It is also possible for a future to take a callback, to be executed 28 | 29 | ```clojure 30 | (let [ferengi-wisdom-promise (promise)] 31 | (future (println "Here's some Ferengi wisdom:" @ferengi-wisdom-promise)) 32 | (Thread/sleep 100) 33 | (deliver ferengi-wisdom-promise "Whisper your way to success.")) 34 | ; => Here's some Ferengi wisdom: Whisper your way to success. 35 | ``` 36 | 37 | ### Rolling my own queue 38 | 39 | We start by defining a macro: 40 | 41 | ```clojure 42 | (defmacro wait 43 | "Sleep `timeout` seconds before evaluating body" 44 | [timeout & body] 45 | `(do (Thread/sleep ~timeout) ~@body)) 46 | ``` 47 | 48 | Then we split up tasks into a concurrent portion and a serialized portion like so: 49 | 50 | ```clojure 51 | (let [saying3 (promise)] 52 | (future (deliver saying3 (wait 100 "Cheerio!"))) 53 | @(let [saying2 (promise)] 54 | (future (deliver saying2 (wait 400 "Pip pip!"))) 55 | @(let [saying1 (promise)] 56 | (future (deliver saying1 (wait 200 "'Ello, gov'na!"))) 57 | (println @saying1) 58 | saying1) 59 | (println @saying2) 60 | saying2) 61 | (println @saying3) 62 | saying3) 63 | ``` 64 | 65 | ...which can be replaced by a macro: 66 | 67 | ```clojure 68 | (defmacro enqueue 69 | ([q concurrent-promise-name concurrent serialized] 70 | `(let [~concurrent-promise-name (promise)] 71 | (future (deliver ~concurrent-promise-name ~concurrent)) 72 | (deref ~q) 73 | ~serialized 74 | ~concurrent-promise-name)) 75 | ([concurrent-promise-name concurrent serialized] 76 | `(enqueue (future) ~concurrent-promise-name ~concurrent ~serialized))) 77 | ``` 78 | 79 | ...which you can use like this: 80 | 81 | ```clojure 82 | (-> (enqueue saying (wait 200 "'Ello, gov'na!") (println @saying)) 83 | (enqueue saying (wait 400 "Pip pip!") (println @saying)) 84 | (enqueue saying (wait 100 "Cheerio!") (println @saying))) 85 | ``` 86 | 87 | The result is: 88 | 89 | ```clojure 90 | (time @(-> (enqueue saying (wait 200 "'Ello, gov'na!") (println @saying)) 91 | (enqueue saying (wait 400 "Pip pip!") (println @saying)) 92 | (enqueue saying (wait 100 "Cheerio!") (println @saying)))) 93 | ; => 'Ello, gov'na! 94 | ; => Pip pip! 95 | ; => Cheerio! 96 | ; => "Elapsed time: 401.635 msecs" 97 | ``` 98 | 99 | ### Chapter 9 Exercises 100 | 101 | 1. Write a function that takes a string as an argument and searches for it on Bing and Google using the slurp function. Your function should return the HTML of the first page returned by the search. 102 | 103 | ```clojure 104 | (defn first-html 105 | [query] 106 | (let [result-promise (promise)] 107 | (future (deliver result-promise (slurp (str "https://www.google.com/search?q%3D" query)))) @result-promise)) 108 | 109 | (re-seq #"https?://[^\"]*" (first-html "hello")) 110 | ``` 111 | 112 | 2. Update your function so it takes a second argument consisting of the search engines to use. 113 | 114 | ```clojure 115 | (defn first-html 116 | [query engine] 117 | (let [result-promise (promise)] 118 | (future (deliver result-promise (slurp (str engine query)))) @result-promise)) 119 | ``` 120 | 121 | 3. Create a new function that takes a search term and search engines as arguments, and returns a vector of the URLs from the first page of search results from each search engine. 122 | 123 | ```clojure 124 | (defn get-urls [query engines] 125 | (vec (flatten (map #(re-seq #"https?://[^\"]*" (first-html %)) engines)))) 126 | ``` 127 | 128 | ## Takeaways 129 | 130 | Futures let you define a task and execute it immediately, allowing you to require the result later or never. Futures also cache their results. Delays let you define a task that doesn’t get executed until later, and a delay’s result gets cached. Promises let you express that you require a result without having to know about the task that produces that result. You can only deliver a value to a promise once. 131 | 132 | Let's end the day with a tally of what I completed: 133 | 134 | - Finished Chapter 9 of Clojure for the Brave and True 135 | - 3 4clojure problems 136 | -------------------------------------------------------------------------------- /posts/2020-07-13.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 4 Day 1 (22/35) 2 | 3 | ## Expectations 4 | 5 | This is the start of week 4 and oh my, has the time gone quickly. I feel like there is so much more to learn... Hopefully I will be done with Brave Clojure Soon, will have reached 100 problems on 4clojure and then will have time to focus on actual Athens issues... Maybe read the reframe documentation. 6 | 7 | Today I hoped to advance as much as I can in Brave Clojure's Chapter 10, and hopefully do some 4clojure problems as well. 8 | 9 | ## What I learned 10 | 11 | ### Functional Programming 12 | 13 | Contrary to OOP, in FP you don't modify an entity, you create a new one derived from the original one. The value doesn't change, you apply a process to a value (or not) to get a new value. 14 | 15 | In this paradigm, state means the value of an identity at a point in time. 16 | 17 | > Rich Hickey has used the analogy of phone numbers to explain state. **Alan’s phone number** has changed 10 times, but we will always call these numbers by the same name, **Alan’s phone number**. Alan’s phone number five years ago is a different value than Alan’s phone number today, and both are two states of Alan’s phone number identity. 18 | 19 | ### Atoms 20 | 21 | ```(def fred (atom {:cuddle-hunger-level 0 22 | :percent-deteriorated 0})) 23 | ``` 24 | 25 | This creates a new atom and binds it to the name fred. This atom **refers** to the value {:cuddle-hunger-level 0 :percent-deteriorated 0}, and you would say that that’s its current state. 26 | To get an atom’s current state, you dereference it. Here’s Fred’s current state: 27 | 28 | ```@fred; 29 | => {:cuddle-hunger-level 0, :percent-deteriorated 0} 30 | ``` 31 | 32 | In this case, dereferencing an atom will never block. 33 | We use `swap!` to update an atom: 34 | 35 | ```clojure 36 | (swap! fred 37 | (fn [current-state] 38 | (merge-with + current-state {:cuddle-hunger-level 1 39 | :percent-deteriorated 1}))) 40 | ; => {:cuddle-hunger-level 2, :percent-deteriorated 1} 41 | ``` 42 | 43 | Or another way is to use `swap!` with the result of a function (and taking a state): 44 | 45 | ```clojure 46 | (swap! fred increase-cuddle-hunger-level 10) 47 | ; => {:cuddle-hunger-level 12, :percent-deteriorated 1} 48 | 49 | @fred 50 | ; => {:cuddle-hunger-level 12, :percent-deteriorated 1} 51 | ``` 52 | 53 | A new function (to me) also helps with updating state, `update-in`: 54 | 55 | ```clojure 56 | (update-in {:a {:b 3}} [:a :b] inc) 57 | ; => {:a {:b 4}} 58 | 59 | (update-in {:a {:b 3}} [:a :b] + 10) 60 | ; => {:a {:b 13}} 61 | ``` 62 | 63 | It can be used like this: 64 | 65 | ```clojure 66 | (swap! fred update-in [:cuddle-hunger-level] + 10) 67 | ; => {:cuddle-hunger-level 22, :percent-deteriorated 1} 68 | ``` 69 | 70 | Another great things about atoms is that you can still access previous versions of the state, like so: 71 | 72 | ```clojure 73 | (let [num (atom 1) 74 | s1 @num] 75 | (swap! num inc) 76 | (println "State 1:" s1) 77 | (println "Current state:" @num)) 78 | ; => State 1: 1 79 | ; => Current state: 2 80 | ``` 81 | 82 | swap! implements **compare-and-set** semantics, meaning it does the following internally: 83 | 84 | - It reads the current state of the atom. 85 | - It then applies the update function to that state. 86 | - Next, it checks whether the value it read in step 1 is identical to the atom’s current value. 87 | - If it is, then swap! updates the atom to refer to the result of step 2. 88 | - If it isn’t, then swap! retries, going through the process again with step 1. 89 | 90 | `swap!` updates do happen synchronously. 91 | 92 | To update a atom without reading its value we can use the `reset!` function, `(reset! fred {:new 0})`. 93 | 94 | ### Watches 95 | 96 | A watch takes 4 arguments: a key, the reference being watched, its previous state, and its new state. It allows to check on a reference type's every move. 97 | 98 | Let's say you have a `shuffle-speed` function: 99 | 100 | ```clojure 101 | (defn shuffle-speed 102 | [zombie] 103 | (* (:cuddle-hunger-level zombie) 104 | (- 100 (:percent-deteriorated zombie)))) 105 | ``` 106 | 107 | And you want to be alerted when the shuffle speed is above a certain level. You can do the following. `add-watch` attaches the function to `fred` 108 | 109 | ````clojure 110 | (defn shuffle-alert 111 | [key watched old-state new-state] 112 | (let [sph (shuffle-speed new-state)] 113 | (if (> sph 5000) 114 | (do 115 | (println "Run, you fool!") 116 | (println "The zombie's SPH is now " sph) 117 | (println "This message brought to your courtesy of " key)) 118 | (do 119 | (println "All's well with " key) 120 | (println "Cuddle hunger: " (:cuddle-hunger-level new-state)) 121 | (println "Percent deteriorated: " (:percent-deteriorated new-state)) 122 | (println "SPH: " sph))))) 123 | 124 | (reset! fred {:cuddle-hunger-level 22 125 | :percent-deteriorated 2}) 126 | (add-watch fred :fred-shuffle-alert shuffle-alert) 127 | (swap! fred update-in [:percent-deteriorated] + 1) 128 | ; => All's well with :fred-shuffle-alert 129 | ; => Cuddle hunger: 22 130 | ; => Percent deteriorated: 3 131 | ; => SPH: 2134 132 | 133 | (swap! fred update-in [:cuddle-hunger-level] + 30) 134 | ; => Run, you fool! 135 | ; => The zombie's SPH is now 5044 136 | ; => This message brought to your courtesy of :fred-shuffle-alert``` 137 | ### Validators 138 | __Validators__ let you specify what states are allowable for a reference. For example, here’s a validator that you could use to ensure that a zombie’s `:percent-deteriorated` is between 0 and 100: 139 | ```(defn percent-deteriorated-validator 140 | [{:keys [percent-deteriorated]}] 141 | (and (>= percent-deteriorated 0) 142 | (<= percent-deteriorated 100))) 143 | ```` 144 | 145 | And this is how you attach a validator: 146 | 147 | ```clojure 148 | (def bobby 149 | (atom 150 | {:cuddle-hunger-level 0 :percent-deteriorated 0} 151 | :validator percent-deteriorated-validator)) 152 | (swap! bobby update-in [:percent-deteriorated] + 200) 153 | ; This throws "Invalid reference state" 154 | ``` 155 | 156 | It's even possible to throw an exception to get a more descriptive message: 157 | 158 | ```clojure 159 | (defn percent-deteriorated-validator 160 | [{:keys [percent-deteriorated]}] 161 | (or (and (>= percent-deteriorated 0) 162 | (<= percent-deteriorated 100)) 163 | (throw (IllegalStateException. "That's not mathy!")))) 164 | (def bobby 165 | (atom 166 | {:cuddle-hunger-level 0 :percent-deteriorated 0} 167 | :validator percent-deteriorated-validator)) 168 | (swap! bobby update-in [:percent-deteriorated] + 200) 169 | ; This throws "IllegalStateException: That's not mathy!" 170 | ``` 171 | 172 | ## Takeaways 173 | 174 | I went a step further on atoms, which is used to manage state. To manage concurrent state updates I will (re)learn `ref`s, which is kind of exciting given the new information I gathered about `atom`s today. 175 | 176 | I learned about validating a reference, or getting notified on changes in a reference. I also learned that atoms can retain their past states (which is pretty exciting if you ask me!). 177 | Let's end the day with a tally of what I completed 178 | 179 | - First half of Chapter 10 of Clojure for the Brave and True 180 | - 2 4clojure problems 181 | -------------------------------------------------------------------------------- /posts/2020-07-14.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 4 Day 2 (23/35) 2 | 3 | ## What I learned 4 | 5 | ### Refs 6 | 7 | Refs allow you to update the state of multiple identities using transaction semantics. These transactions have three features: 8 | 9 | - They are **atomic**, meaning that all refs are updated or none of them are. 10 | - They are **consistent**, meaning that the refs always appear to have valid states. A sock will always belong to a dryer or a gnome, but never both or neither. 11 | - They are **isolated**, meaning that transactions behave as if they executed serially; if two threads are simultaneously running transactions that alter the same ref, one transaction will retry. This is similar to the compare-and-set semantics of atoms. 12 | 13 | We're going to walk through sock transfer example from Chapter 10 of Brave Clojure: 14 | 15 | ````clojure 16 | (def sock-varieties 17 | #{"darned" "argyle" "wool" "horsehair" "mulleted" 18 | "passive-aggressive" "striped" "polka-dotted" 19 | "athletic" "business" "power" "invisible" "gollumed"}) 20 | 21 | (defn sock-count 22 | [sock-variety count] 23 | {:variety sock-variety 24 | :count count}) 25 | 26 | (defn generate-sock-gnome 27 | "Create an initial sock gnome state with no socks" 28 | [name] 29 | {:name name 30 | :socks #{}}) 31 | 32 | (def sock-gnome (ref (generate-sock-gnome "Barumpharumph"))) 33 | (def dryer (ref {:name "LG 1337" 34 | :socks (set (map #(sock-count % 2) sock-varieties))}))``` 35 | Then we can deference our refs, like this: 36 | ```clojure 37 | (:socks @dryer) 38 | ; => #{{:variety "passive-aggressive", :count 2} {:variety "power", :count 2} 39 | {:variety "athletic", :count 2} {:variety "business", :count 2} 40 | {:variety "argyle", :count 2} {:variety "horsehair", :count 2} 41 | {:variety "gollumed", :count 2} {:variety "darned", :count 2} 42 | {:variety "polka-dotted", :count 2} {:variety "wool", :count 2} 43 | {:variety "mulleted", :count 2} {:variety "striped", :count 2} 44 | {:variety "invisible", :count 2}} 45 | ```` 46 | 47 | To modify a ref we use `alter` which needs to be used within a transaction that you can initiate with `dosync`. 48 | 49 | ```clojure 50 | (defn steal-sock 51 | [gnome dryer] 52 | (dosync 53 | (when-let [pair (some #(if (= (:count %) 2) %) (:socks @dryer))] 54 | (let [updated-count (sock-count (:variety pair) 1)] 55 | (alter gnome update-in [:socks] conj updated-count) 56 | (alter dryer update-in [:socks] disj pair) 57 | (alter dryer update-in [:socks] conj updated-count))))) 58 | (steal-sock sock-gnome dryer) 59 | 60 | (:socks @sock-gnome) 61 | ; => #{{:variety "passive-aggressive", :count 1}} 62 | ``` 63 | 64 | ### Commute 65 | 66 | `commute` allows you to update a ref’s state within a transaction, just like alter. However, its behavior at commit time is completely different. Here’s how alter behaves: 67 | 68 | - Reach outside the transaction and read the ref’s current state. 69 | - Compare the current state to the state the ref started with within the transaction. 70 | - If the two differ, make the transaction retry. 71 | - Otherwise, commit the altered ref state. 72 | commute, on the other hand, behaves like this at commit time: 73 | - Reach outside the transaction and read the ref’s current state. 74 | - Run the commute function again using the current state. 75 | - Commit the result. 76 | This is how to use a `ref`: 77 | 78 | ```clojure 79 | (defn sleep-print-update 80 | [sleep-time thread-name update-fn] 81 | (fn [state] 82 | (Thread/sleep sleep-time) 83 | (println (str thread-name ": " state)) 84 | (update-fn state))) 85 | (def counter (ref 0)) 86 | (future (dosync (commute counter (sleep-print-update 100 "Thread A" inc)))) 87 | (future (dosync (commute counter (sleep-print-update 150 "Thread B" inc)))) 88 | ``` 89 | 90 | ### Vars 91 | 92 | `vars` are associations between symbols and objects. They are created with `def`. 93 | 94 | #### Dynamic binding 95 | 96 | You can acually create a dynamic binding with `^:dynamic` in front of the name and the name itself is enclosed in asteriks: 97 | 98 | ```clojure 99 | (def ^:dynamic *notification-address* "dobby@elf.org") 100 | ``` 101 | 102 | And this is how you change it: 103 | 104 | ```(binding [*notification-address* "test@elf.org"] 105 | *notification-address*); => "test@elf.org" 106 | ``` 107 | 108 | It behaves kind of like a `let`, the var is changed only within the `binding` expression. 109 | 110 | Dynamic vars can be used for function targets, like `*out*`. Dynamic vars are also used for configuration. For example, the built-in var *print-length* allows you to specify how many items in a collection Clojure should print: 111 | 112 | ```(println ["Print" "all" "the" "things!"]) 113 | ; => [Print all the things!] 114 | 115 | (binding [*print-length* 1] 116 | (println ["Print" "just" "one!"])) 117 | ; => [Print ...] 118 | ``` 119 | 120 | `set!` can also be used to set a dynamic binding's value. 121 | 122 | #### Altering the var root 123 | 124 | The var root is the initial value that you supply to `def`. And for this we need to use `alter-var-root`, like this: 125 | 126 | ```clojure 127 | (def power-source "hair") 128 | (alter-var-root #'power-source (fn [_] "7-eleven parking lot")) 129 | 130 | power-source 131 | ; => "7-eleven parking lot" 132 | ``` 133 | 134 | ### Stateless Concurrency and Parallelism with pmap 135 | 136 | `pmap` is basically `map` with added parallelism. With `pmap` each application of the mapping is handled on a separate thread. 137 | 138 | ### Brave Clojure Chapter 10 Exercises 139 | 140 | #### 1. Create an atom with the initial value 0, use swap! to increment it a couple of times, and then dereference it. 141 | 142 | ````clojure 143 | (def my-atom (atom 0)) 144 | (swap! my-atom (fn [current-value] (inc current-value))) 145 | (swap! my-atom (fn [current-value] (inc current-value))) 146 | (swap! my-atom (fn [current-value] (inc current-value))) 147 | @my-atom``` 148 | #### 2. Create a function that uses futures to parallelize the task of downloading random quotes from__http://www.braveclojure.com/random-quote__ using (slurp "http://www.braveclojure.com/random-quote"). The futures should update an atom that refers to a total word count for all quotes. The function will take the number of quotes to download as an argument and return the atom’s final value. Keep in mind that you’ll need to ensure that all futures have finished before returning the atom’s final value. 149 | ```clojure 150 | (def quote-word-frequencies (atom {})) 151 | 152 | (defn word-frequencies [string] 153 | (frequencies (clojure.string/split string #"\W+"))) 154 | 155 | (defn get-quote-and-add-word-frequencies [] 156 | (swap! quote-word-frequencies (fn [current-state] (merge-with + current-state (word-frequencies (slurp "https://braveclojure.com/random-quote")))))) 157 | 158 | (defn create-futures 159 | [n] 160 | (repeatedly n #(future get-quote-and-word-frequency))) 161 | 162 | (defn get-futures 163 | [futures] 164 | (dorun (pmap deref futures))) 165 | 166 | (defn quote-word-count [n] 167 | (get-futures (create-futures n))) 168 | 169 | (quote-word-count 5) 170 | ```` 171 | 172 | At this point this will not work, and I am not sure why. I spent some time trying to debug it and I will come back and fix it. 173 | 174 | #### 3. Create representations of two characters in a game. The first character has 15 hit points out of a total of 40. The second character has a healing potion in his inventory. Use refs and transactions to model the consumption of the healing potion and the first character healing. 175 | 176 | ```clojure 177 | (def char1 (ref {:healing_potion 1})) 178 | (def char2 (ref {:health 15})) 179 | (def max-health 40) 180 | 181 | (defn healing 182 | [healer receiver] 183 | (dosync 184 | (alter healer update-in [:healing_potion] dec) 185 | (alter receiver assoc-in [:health] max-health))) 186 | 187 | (healing char1 char2) 188 | 189 | (print @char1) 190 | (print @char2) 191 | ``` 192 | 193 | ## Takeaways 194 | 195 | In this chapter, I reviewed ways to safely handling concurrent tasks. State is the value of an identity at a point in time, and identity is a handy way to refer to a succession of values produced by some process. 196 | 197 | The atom allows to create an identity that we can safely refer to, update with new values with `swap!` or `reset!`. 198 | 199 | The ref reference type is useful when there are more than one reference type to update (with `alter!` or `commute!`). 200 | 201 | Let's end the day with a tally of what I completed: 202 | 203 | - Finished Chapter 10 of Clojure for the Brave and True 204 | - Also covered its exercises (despite the fact that I have to come back to exercise 2) 205 | -------------------------------------------------------------------------------- /posts/2020-07-15.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 4 Day 3 (24/35) 2 | 3 | ## Expectations 4 | 5 | There are no more exercises in Brave Clojure so my goal now is to finish a chapter a day so I can be done with the book soon. So today the expectation is Chapter 11. 6 | 7 | ## What I learned 8 | 9 | ### Getting started with processes 10 | 11 | `go` creates a new process, and the go expression is called a go block. Everything in the go block runs concurrently, on a seperate thread. If you have a channel create by `chan`, then evaluating `(println (!` is the put function `(>!! echo-chan "stuff")` puts a message in `echo-chan` and the thread is blocked until it's read. Double the `!` and you can use it outside of the go block. 15 | And the code looks like this: 16 | 17 | ```clojure 18 | (def echo-chan (chan)) 19 | (go (println (!! echo-chan "ketchup") 21 | ; => true 22 | ; => ketchup 23 | ``` 24 | 25 | #### Buffering 26 | 27 | It is possible to buffer messages, by passing a number `n` to the `chan` function: 28 | 29 | ```clojure 30 | (def echo-buffer (chan 2)) 31 | (>!! echo-buffer "ketchup") 32 | ; => true 33 | (>!! echo-buffer "ketchup") 34 | ; => true 35 | (>!! echo-buffer "ketchup") 36 | ; This blocks because the channel buffer is full 37 | ``` 38 | 39 | This will be unblocked if another process takes a message from the channel. There are also `sliding-buffer`s which are FIFO, and `dropping-buffer`s which are LIFO. 40 | 41 | #### `threads` 42 | 43 | `threads` act almost exactly like futures: it creates a new thread and executes a process on that thread. Unlike future, instead of returning an object that you can dereference, thread returns a channel. When thread’s process stops, the process’ return value is put on the channel that thread returns: 44 | 45 | The author of the book has a good explanation of why we should use `thread`s over go blocks when running long computations: 46 | 47 | > The reason you should use thread instead of a go block when you’re performing a long-running task is so you don’t clog your thread pool. Imagine you’re running four processes that download humongous files, save them, and then put the file paths on a channel. While the processes are downloading files and saving these files, Clojure can’t park their threads. It can park the thread only at the last step, when the process puts the files’ paths on a channel. Therefore, if your thread pool has only four threads, all four threads will be used for downloading, and no other process will be allowed to run until one of the downloads finishes. 48 | 49 | #### `alts!!` 50 | 51 | `alts!!` takes a vector of channels as argument and it will take the result of the first one to have anything on it. It is also possible to give it a timeout like this `(alts!! [c1 (timeout 20)])`. `alts!` is the parking alternative to `alts!!`. 52 | 53 | ### 4clojure problem: is the tree symmetric? 54 | 55 | ```clojure 56 | (fn symmetric? [tree] 57 | (= tree ((fn mirror [t] (when t [(first t) (mirror (last t)) (mirror (second t))])) tree))) 58 | ``` 59 | 60 | ## Takeaways 61 | 62 | Today I learned about `core.async` allows me to create concurrent processes that respond to put and take communication events on channels. I also learned about go and thread. It's amazing that this library was inspired by Go's concurrency model. 63 | Let's end the day with a tally of what I completed: 64 | 65 | - Chapter 11 of Clojure for the Brave and True 66 | - 4 4clojure problems (I am not done with the easy problems!) 67 | -------------------------------------------------------------------------------- /posts/2020-07-16.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 4 Day 4 (25/35) 2 | 3 | ## Expectations 4 | 5 | I originally planned on tackling the second to last chapter of Clojure for the Brave and True (Chapter 12) today which would have taken me one step closer to the goal of finishing it this week but duty calls and [our communal issue](https://github.com/athensresearch/athens/issues/126) (within my ClojureFam cohort) needs to be addressed! I will therefore do some more research on my own as to what is causing the issue so we can discuss it in a call. Whatever extra time I find myself lucky to have shall be used for some casual reading that unlucky chapter (it's about the JVM and the Java interop). 6 | 7 | ## What I learned 8 | 9 | Clojure being built on the JVM brings three characteristics to the language: 10 | 11 | - Clojure applications run where Java applications run 12 | - Clojure uses Java object for core functionality like reading a file 13 | - Clojure can leverage Java's vast library ecosystem 14 | The JVM reads Java bytecode. It then translates it on the fly to machine code that the host will understand. It is **just in time compilation**. 15 | 16 | ### Java Interop 17 | 18 | Here is how you call a method on an object: 19 | 20 | ```clojure 21 | (.toUpperCase "hello world") ; => "HELLO WORLD" 22 | (.indexOf "hello world" "w") ; => 6 23 | (java.lang.Math/abs -2) ; => 2 24 | ``` 25 | 26 | These examples use macro that expand to use the dot special form. 27 | 28 | You can also create new objects: 29 | 30 | ```clojure 31 | (new String) 32 | (String.) ; this is equivalent 33 | (String. "a string") 34 | ``` 35 | 36 | Then you can also do more interesting things like, use a Java stack: 37 | 38 | ```clojure 39 | (java.util.Stack.) 40 | ; => [] 41 | 42 | (let [stack (java.util.Stack.)] 43 | (.push stack "Latest episode of Game of Thrones, ho!") 44 | stack) 45 | ; => ["Latest episode of Game of Thrones, ho!"] 46 | ``` 47 | 48 | One can also import like this 49 | 50 | ```(import java.util.Stack) 51 | (Stack.) 52 | ; => [] 53 | ``` 54 | 55 | Or even multiple at once 56 | 57 | ```(import [java.util Date Stack] 58 | [java.net Proxy URI]) 59 | 60 | (Date.) 61 | ; => #inst "2016-09-19T20:40:02.733-00:00" 62 | ``` 63 | 64 | That you can also do with `ns` 65 | 66 | ```(ns pirate.talk 67 | (:import [java.util Date Stack] 68 | [java.net Proxy URI])) 69 | ``` 70 | 71 | #### Commonly used Java classes 72 | 73 | - The `System` class lets you interact with the environment 74 | - `exit`, `getenv`, `getProperty` 75 | 76 | ```clojure 77 | (System/getProperty "user.dir") 78 | ; => "/Users/dabulk/projects/dabook" 79 | 80 | (System/getProperty "java.version") 81 | ; => "1.7.0_17" 82 | ``` 83 | 84 | - The `Date` class, `java.util.Date` is useful to tweak the you want a date to be converted to string. 85 | 86 | ### `clojure.java.io` 87 | 88 | To read and write to a file, you `spit` and `slurp`: 89 | 90 | ```clojure 91 | (spit "/tmp/hercules-todo-list" 92 | "- kill dat lion brov 93 | - chop up what nasty multi-headed snake thing") 94 | 95 | (slurp "/tmp/hercules-todo-list") 96 | 97 | ; => "- kill dat lion brov 98 | ; - chop up what nasty multi-headed snake thing" 99 | ``` 100 | 101 | This will also work on objects other than files. 102 | 103 | ```clojure 104 | (let [s (java.io.StringWriter.)] 105 | (spit s "- capture cerynian hind like for real") 106 | (.toString s)) 107 | ; => "- capture cerynian hind like for real" 108 | 109 | (let [s (java.io.StringReader. "- get erymanthian pig what with the tusks")] 110 | (slurp s)) 111 | ; => "- get erymanthian pig what with the tusks" 112 | ``` 113 | 114 | ### Troubleshooting our second communal issue 115 | 116 | We (Team Seneca, of ClojureFam) made some progress on our issue today. Part of our issue is that when browsing the results in Athena (the search function) it is possible for the selected item to not be visible (the list doesn't scroll to the item). 117 | 118 | Jeff suggested we use [scrollIntoView](https://www.w3schools.com/jsref/met_element_scrollintoview.asp) which actually gets us somewhere. The idea is to grab the element with `getElementByClassName` and then run `scrollIntoView`. We can place this code is the key up and key down event handler in `athena.cljs`: 119 | 120 | ```clojure 121 | (= key KeyCodes.UP) 122 | (do 123 | (swap! state update :index dec) 124 | (let [cur-sel (first (array-seq (. js/document getElementsByClassName "selected")))] 125 | (.. cur-sel (scrollIntoView false {:behavior "smooth" :block "center"})))) 126 | 127 | (= key KeyCodes.DOWN) 128 | (do 129 | (swap! state update :index inc) 130 | (let [cur-sel (first (array-seq (. js/document getElementsByClassName "selected")))] 131 | (.. cur-sel (scrollIntoView true {:behavior "smooth" :block "center"})))) 132 | ``` 133 | 134 | There are still some issues: the scrolling actually happens, but it's too eager. It also happens when the element wasn't going to leave the viewport. It needs to happen only the element does leave it. 135 | 136 | [This comment](https://github.com/athensresearch/athens/issues/126#issuecomment-659858405) by @nthd3gr33 is a good summary of where we stand so far. 137 | 138 | ## Takeaways 139 | 140 | In this chapter I learned what it means for Clojure to be hosted on the JVM. I also looked into using Java classes in Clojure. I wonder if the same happens with ClojureScript and one is able to use JavaScript methods in ClojureScript. 141 | 142 | It's also quite refreshing to be working on an issue. I'm quite happy to have only one chapter of Brave Clojure to read (which hopefully I will do tomorrow). It will be the opportunity to start working on my own issue, while also bridging the gap between Clojure and the Athens web application in terms of knowledge. 143 | 144 | Let's end the day with a tally of what I completed: 145 | 146 | - Chapter 11 of Clojure for the Brave and True 147 | - Some progress on issue 126 in Athens 148 | -------------------------------------------------------------------------------- /posts/2020-07-19.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 4 Day 7 (28/35) 2 | 3 | ## Expectations 4 | 5 | Low expectations today because, it is a Sunday, and because I no longer have the thread of a specific documentation, or book to follow so I am taking shots in the dark, trying to make things work and learn something at the same time. 6 | 7 | The expectation for today is to start building a small re-frame application. Something very simple. It would have made sense for me to build alongside reading the documentation but there's only so many hours in a day, it had to be split and this how I did it. For sure this is improving my understanding of re-frame. 8 | 9 | ## What I learned while building 10 | 11 | I started by setting up my views. In my views I needed a wheel, to display a message, and also the result of the spin. For starters, the wheel is simply a button that says "spin". 12 | 13 | ```clojure 14 | (defn wheel [] 15 | (let [yn (re-frame/subscribe [::subs/yn])] 16 | [:div 17 | [:h3 "The wheel says " @yn] 18 | [:input {:type "button" :value "Spin" 19 | :on-click #(re-frame.core/dispatch [:spin])}]])) 20 | ``` 21 | 22 | The view is subscribing to the value of `yn` in the database. I also added a random message, with wheel themed pun (I got a lot more puns coming!). 23 | 24 | ```clojure 25 | (def sayings ["Der wheel zur macht" 26 | "Wheel power" 27 | "Boys wheel be boys" 28 | "Free wheel offering" 29 | "Time wheel tell" 30 | "Wheel Wheaton"]) 31 | 32 | (defn the-wheel-says [] 33 | (sayings (int (rand (count sayings))))) 34 | ``` 35 | 36 | Finally we can tie this up together in the main element: 37 | 38 | ```clojure 39 | (defn main-panel [] 40 | [:div 41 | [:h1 "Hail the Wheel!"] 42 | [:h2 [the-wheel-says]] 43 | [wheel]]) 44 | ``` 45 | 46 | After that I need to create the event, that happens on click: 47 | 48 | ```clojure 49 | (def yes-no-string ["YES" "NO"]) 50 | 51 | (re-frame/reg-event-db 52 | :spin 53 | (fn [db] 54 | (let [spin-result (yes-no-string (int (rand 2)))] 55 | (js/console.log "Spin result:" spin-result) 56 | (assoc db :yn spin-result)))) 57 | ``` 58 | 59 | However, the event handler should remain a pure function so for this I should be using a coeffect. 60 | 61 | ```clojure 62 | (re-frame/reg-cofx 63 | :yn 64 | (fn [cofx _] 65 | (assoc-in cofx [:db :yn] (yes-no-strings (int (rand 2)))))) 66 | ``` 67 | 68 | However, since I am not just merely updating the `db` I need to switch the `reg-event-db` to a `reg-event-fx`. [This StackOverflow answer](https://stackoverflow.com/a/54864938) has more details: 69 | 70 | > If your handler needs to access co-effects/produce effects then you'd 71 | > use reg-event-fx and get the :coeffects value (and :db if necessary) 72 | > from the handler's input. A common use case is when you need to access 73 | > browser storage (e.g. cookies, local storage) but want to keep your 74 | > handlers free of side-effects. 75 | > So we end up with: 76 | 77 | ```clojure 78 | (re-frame/reg-event-fx 79 | :spin 80 | [(re-frame/inject-cofx :yn)] 81 | (fn [cofx _] 82 | {:db (assoc (:db cofx) :yn (:yn cofx))})) 83 | 84 | (re-frame/reg-cofx 85 | :yn 86 | (fn [cofx _] 87 | (assoc cofx :yn (yes-no-strings (int (rand 2)))))) 88 | ``` 89 | 90 | ## Takeaways 91 | 92 | Putting together this very simple re-frame loop was easy to get started with but understanding how to use the coeffects was a lot more complicated, which is ironic because it was not really needed in case, yet I wanted to be as as close to real world conditions as possible. 93 | 94 | Tomorrow I will focus on styling the wheel, maybe make it spin as well! 95 | -------------------------------------------------------------------------------- /posts/2020-07-20.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 5 Day 1 (29/35) 2 | 3 | ## Expectations 4 | 5 | Today the expectation is to make some progress on the styling of my wheel of fortune and to get my marks on [our new issue](https://github.com/athensresearch/athens/issues/96). 6 | 7 | ## What I Did 8 | 9 | ### Our issue 10 | 11 | There are five sub issues in our larger one. They are all about pages. For some of them we need to figure out the UX workflow associated with them. 12 | 13 | #### Deleting pages 14 | 15 | We need to create a `:page/delete` event. 16 | We may need to add a column with the delete icon on the all page view. 17 | Roam lets you select pages and you can delete one, or several, also select for exporting. It may be worthwhile to do this as well, because it will let us do bulk actions as well as other actions instead of adding columns like crazy. 18 | Deleting a page also strips the links from the pages that reference it. 19 | Deleting the links to a page that was never accessed also deletes the page. 20 | 21 | #### Adding a page to shortcuts 22 | 23 | We need to add a button on the page (top right maybe?) 24 | 25 | Roam's design 26 | 27 | ![](https://firebasestorage.googleapis.com/v0/b/firescript-577a2.appspot.com/o/imgs%2Fapp%2Flacqad%2Fbg6BeZkPJA.png?alt=media&token=b3d97a8e-9f7a-4df5-af12-1778f5d96bfa) 28 | 29 | Ideas from our [Figma](https://www.figma.com/file/iCXP6z7H5IAQ6xyFr5AbZ7/Product-Design-Sandbox?node-id=524%3A1389) 30 | 31 | ![](https://firebasestorage.googleapis.com/v0/b/firescript-577a2.appspot.com/o/imgs%2Fapp%2Flacqad%2FJfr09bI_2_.png?alt=media&token=9c7fc273-8d6c-42ce-8e41-d4c2ad6fa715) 32 | 33 | `left_sidebar.cljs` has the [posh/datalog query](https://github.com/athensresearch/athens/blob/35b101db77ec5e846364af4fa40c9a0413e1928a/src/cljs/athens/views/left_sidebar.cljs#L142L148), we need to add the datascript database. 34 | 35 | We probably need a an event for adding a shortcut. We can take inspiration from the `:page/create` event to create shortcut. 36 | 37 | #### Button to create a new page 38 | 39 | Was this a feature request? 40 | Presented in the form of a question: 41 | 42 | ![](https://firebasestorage.googleapis.com/v0/b/firescript-577a2.appspot.com/o/imgs%2Fapp%2Flacqad%2FDvRgaLypDh.png?alt=media&token=6895539e-088a-450e-ac48-269ca11d37e9) 43 | 44 | This should be fairly simple, take the same workflow as creating a page from athena. We can create the button, then run the same event handler. However, it will have to be an unnamed page? There is no way to create a page in Roam with the click of a button. It is done via tag or the search bar. 45 | 46 | There is a [reg-event-fx](https://github.com/athensresearch/athens/blob/35b101db77ec5e846364af4fa40c9a0413e1928a/src/cljs/athens/events.cljs#L277) in events.cljs 47 | 48 | #### Merging pages 49 | 50 | This is a bigger project and it has separate issues. 51 | 52 | #### Filters 53 | 54 | Filters are also a much bigger undertaking. 55 | 56 | #### Our first pick: deleting a page 57 | 58 | None of us is into adding a button to create a page, and adding and removing pages to shortcut was actually already implemented, so the natural choice for us now was to tackle page deletion. 59 | 60 | We came up with [a few questions](https://github.com/athensresearch/athens/issues/96#issuecomment-661636355), which we hope to get answers for soon: 61 | 62 | - In Roam, you can check pages in the All Pages list and then you can 63 | bulk delete. Do we also want to implement that here? Or are bulk 64 | operations out of the scope of this issue? 65 | - What happens to empty pages that have no links to them. Do we keep them? Eagerly delete them? 66 | - If you delete a page, should all corresponding links to it be deleted? (this is current Roam behavior) 67 | 68 | ### Styling and animating my decide wheel 69 | 70 | The wheel now lives in [its own repo](https://github.com/alaq/hail-the-wheel) (its needs are very real!) and was transformed from a imaginary wheel (just a button) to a less imaginary one, one you can see on the screen. 71 | 72 | I am not going to detail what I did because it was mostly CSS. I did use Garden which has a hiccup link syntax but it was mostly applying CSS nevertheless. The changes are [here](https://github.com/alaq/hail-the-wheel/commit/3f2ea9cbb3f4aa1d62e34a6433b3e927d59cdbb9). 73 | 74 | Also, it now spins! 75 | 76 | ## Takeaways 77 | 78 | I am a lot less guided in what I am learning but the exploration is all the more interesting. There are a lot of aha moments, things I thought I understood while reading that I finally actually understand. So it is really gratifying. 79 | Let's end the day with a tally of what I completed: 80 | 81 | - Figured out what we needed to know to make progress on our issue 82 | - Displayed the wheel on the screen, styled it and made it spin. 83 | -------------------------------------------------------------------------------- /posts/2020-07-21.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 5 Day 2 (30/35) 2 | 3 | ## Expectations 4 | 5 | I decided to rebuild my wheel from scratch (part of my decide-wheel clone). I am completely bike-shedding here, but if I can turn this into something I'll use (yes, I use the decide wheel often), it is still an interesting experience (not really, I am lying, it's CSS). Therefore I don't expect any interesting update on that (or a very beautiful wheel, who knows!). 6 | 7 | On the other hand the expectation is to make good progress on page deletion in Athens. 8 | 9 | ## What I learned 10 | 11 | ### Deleting pages in Athens 12 | 13 | There is currently a button to delete a page within the drop down on the page itself (where you can also add a page to your favorites), so the first step is to add a re-frame event handler, in `events.cljs`: 14 | 15 | ```clojure 16 | (reg-event-fx 17 | :page/delete 18 | (fn [_ [_ uid]] 19 | (js/console.log uid) 20 | {:transact! [[:db/retract [:block/uid uid] :block/uid]]})) 21 | ``` 22 | 23 | We then need to call this event handler on click on that button in the dropdown, in `node_page.cljs`: 24 | 25 | ```diff 26 | - [button {:disabled true} 27 | + [button {:on-click #(dispatch [:page/delete uid])} 28 | [:<> [:> mui-icons/Delete] [:span "Delete Page"]]]]]) 29 | ``` 30 | 31 | Upon deleting the page, we remain on the same url hash, and the 404 message is displayed. We should instead redirect to another page, like `All Pages`: 32 | 33 | ```diff 34 | - [button {:on-click #(dispatch [:page/delete uid])} 35 | + [button {:on-click #(do 36 | + (navigate :pages) 37 | + (dispatch [:page/delete uid]))} 38 | ``` 39 | 40 | We redirect first to avoid showing the 404 and then redirecting. You can still see it, sadly, so this solution needs to be perfected. 41 | 42 | The team then had a call, to try to write an algorithm to recursively delete pages. We decided a breath first search was probably the best idea. I will sleep on that and learn more about `loop` and `recur` tomorrow to and write the recursive solution. 43 | 44 | ### The wheel 45 | 46 | Regarding the wheel, most of the progress is with the CSS, it does look much better right now, but not very interesting in relations to Clojure, so here is just a picture for now: 47 | 48 | ![](https://firebasestorage.googleapis.com/v0/b/firescript-577a2.appspot.com/o/imgs%2Fapp%2Flacqad%2FORjzQeJKFd.png?alt=media&token=7c4a75a8-b2fa-46b1-9714-c26ff5b7699e) 49 | -------------------------------------------------------------------------------- /posts/2020-07-22.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 5 Day 3 (31/35) 2 | 3 | ## Expectations 4 | 5 | The only expectation of the day is to open the pull request for the page deletion issue in Athens. 6 | 7 | ## What I learned 8 | 9 | And open the PR we did, and even earlier than I expected. Here is a rundown of the most recent changes. First I added Mani's function to get the children of a block from its `uid`: 10 | 11 | ```clojure 12 | (defn get-children-recursively 13 | "Get list of children UIDs for given block ID" 14 | [uid] 15 | (let [document (->> @(pull dsdb '[:block/order :block/uid {:block/children ...}] (get-id uid)))] 16 | (->> (tree-seq :block/children :block/children document) 17 | (map :block/uid)))) 18 | ``` 19 | 20 | He also wrote a helper function to get the database id, from the `uid`: 21 | 22 | ```clojure 23 | (defn get-id 24 | [uid] 25 | (-> (d/q '[:find ?id 26 | :in $ ?uid 27 | :where [?id :block/uid ?uid]] 28 | @dsdb 29 | uid) 30 | ffirst)) 31 | ``` 32 | 33 | Finally we use the function itself in the event handler so we can delete all the `UID`s retrieved by `get-children-recursively`. Datascript's `:transact!` can take a vector of vectors to batch delete several entries in the database (hence the use of `vec`, since `map` returns a list). 34 | 35 | ```clojure 36 | (reg-event-fx 37 | :page/delete 38 | (fn [_ [_ uid]] 39 | {:transact! (vec (map (fn [uid] [:db/retract [:block/uid uid] :block/uid]) (get-children-recursively uid)))})) 40 | ``` 41 | 42 | The [PR](https://github.com/athensresearch/athens/pull/295) has been opened and is awaiting feedback. 43 | 44 | ## Takeaways 45 | 46 | Not much to bring up and to takeaway. I am learning a lot more about how the different pieces I studied fit together. It really feels like the fun stuff is happening right now. Yet there is still a lot of ground to cover. A few things I want to dig deeper into are datascript, paredit (to edit faster!) and datalog (again)! 47 | -------------------------------------------------------------------------------- /posts/2020-07-23.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 5 Day 4 (32/35) 2 | 3 | ## What I Did 4 | 5 | ### Introduction to linked filter implementation 6 | 7 | Similarly to Roam Research, we want to implement linked filters. Linked filters let a user filter through either the content of a page, the linked references to a page, or the content of a page that is in the sidebar. 8 | 9 | ![](https://camo.githubusercontent.com/5a86f035582f1954f0445ebec7d45c97bc45ebba/68747470733a2f2f666972656261736573746f726167652e676f6f676c65617069732e636f6d2f76302f622f666972657363726970742d35373761322e61707073706f742e636f6d2f6f2f696d67732532466170702532466a65666674616e672532467230787147654163416c2e706e673f616c743d6d6564696126746f6b656e3d65656232313339392d313263652d343766382d386163612d366236666136623838373961) 10 | 11 | From the page, or the linked references, the tags need to be extracted. Then from that list a user can build two sub-lists: one to exclude elements (from being displayed), one to include elements. 12 | 13 | The proposed design in Athens is a little bit different from the one in Roam Research (in a good way!): 14 | 15 | ![](https://user-images.githubusercontent.com/8952138/85925407-414d9580-b866-11ea-88d9-2a5ef68a9a4b.png) 16 | 17 | Amazingly, this list has already been [implemented in the devcards](https://athensresearch.github.io/athens/cards.html#!/athens.devcards.filters). 18 | 19 | ### What are the next steps? 20 | 21 | I see three clear next tasks: 22 | 23 | - The filter component needs to be displayed by clicking on the filter icon. The icon is actually missing when a page is displayed in the sidebar, and on pages themselves it needs to be added. Are there any other popup we can take a look at so that we know how to display the component? 24 | - The list needs to be populated with the links in that page (or the links in the linked references if that's where we are). The parsing will probably be done in the event handler, when launching the popup. 25 | - Finally, the bigger task will be filter out the blocks that are not supposed to be shown. This one begs a few questions, such as, should we display the children of a block that has an excluded tag, if the children have an included tag? (the answer is no) 26 | 27 | ### Code exploration 28 | 29 | The previous link was a link to the devcard for filters. If you look at `filters.cljs` in the devcards folder you will see it merely passes dummy data to the component. It shows up the shape the data structure we're passing down. It looks like this: 30 | 31 | ```clojure 32 | (def items 33 | {"Amet" {:count 6 :state :added} 34 | "At" {:count 130 :state :excluded} 35 | "Diam" {:count 6} 36 | ; ... 37 | "Vitae" {:count 1}}) 38 | ``` 39 | 40 | The `filters.cljs` file in the `views` folder contains the css and the Reagent components. The `filters-el` component contains a Reagent atom which is used to store the sort, the search and the items. The component takes a `uid` (of the page) and the list of item. The parsing and extraction of the items must happen somewhere else. A solution would be to use a coeffect, or extract them in the event handler before we display the popup? The problem with the event handler is that you only make the computation when you click? The page could be edited and we most likely want to be reactive to that. What if a tag is added to the page, you will want this to be reflected in the filters popup. 41 | 42 | The second question is how to send back the results of our filters to the page, or the linked references? 43 | 44 | ### Additional styling of the wheel 45 | 46 | I took a few minutes to make sure the wheel looks better, and it also spins in only one direction! 47 | ![](https://firebasestorage.googleapis.com/v0/b/firescript-577a2.appspot.com/o/imgs%2Fapp%2Flacqad%2FaLLH5vvRIX.jpg?alt=media&token=b9ec9c5e-826b-4402-bbc1-12adc1679e99) 48 | -------------------------------------------------------------------------------- /posts/2020-07-24.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 5 Day 5 (33/35) 2 | 3 | Wow time flies, it's almost the end of ClojureFam... 4 | 5 | ## What I Did 6 | 7 | ### Fixing scrolling in slash commands in Athens 8 | 9 | The first thing I did was to identify the the place where the up and down keys were handled. That took place in `keybindings.cljs`. We need to wrap the selection to the first element when we reach the bottom, and to the top when we reach the bottom. 10 | 11 | ```diff 12 | (= type :slash) (cond 13 | (= :up direction) (do 14 | (.. e preventDefault) 15 | + (if (= index 0) 16 | + (swap! state assoc :search/index (dec (count slash-options))) 17 | + (swap! state update :search/index dec)))) 18 | - (swap! state update :search/index dec)) 19 | 20 | (= :down direction) (do 21 | (.. e preventDefault) 22 | + (if (= index (dec (count slash-options))) 23 | + (swap! state assoc :search/index 0) 24 | + (swap! state update :search/index inc)))) 25 | - (swap! state update :search/index inc))) 26 | 27 | ``` 28 | 29 | The next step is to scroll the slash commands container so that the selected elements always remain in the viewport. For this we can use the function that we created in one of our previous pull requests, `is-beyond-rect?`. It takes the next element, and the container and determines whether we need to call `scrollIntoView`. 30 | 31 | An easy way to get the next element is actually to do the swap before any other operation so that the element that has the correct class is actually the one you want to pass to the function. The code becomes this: 32 | 33 | ```diff 34 | (= type :slash) (cond 35 | - (= :up direction) (let 36 | - [index (:search/index @state) 37 | - container (. js/document getElementsByClassName "command-container") 38 | - next-el (nth (array-seq (.. container -children)) index)] 39 | + (= :up direction) (do 40 | (.. e preventDefault) 41 | - (if (= index 0) 42 | - (swap! state assoc :search/index (dec (count slash-options))) 43 | - (swap! state update :search/index dec)) 44 | + (swap! state update :search/index #(dec (if (zero? %) (count slash-options) %))) 45 | + (let [cur-index (:search/index @state) 46 | + container-el (. js/document getElementById "command-container") 47 | + next-el (nth (array-seq (.. container-el -children)) cur-index)] 48 | + (when (is-beyond-rect? next-el container-el) 49 | + (.. next-el (scrollIntoView (not= cur-index (dec (count slash-options))) {:behavior "auto"}))))) 50 | - ) 51 | (= :down direction) (do 52 | (.. e preventDefault) 53 | - (if (= index (dec (count slash-options))) 54 | - (swap! state assoc :search/index 0) 55 | - (swap! state update :search/index inc)))) 56 | + (swap! state update :search/index #(if (= % (dec (count results))) 0 (inc %))) 57 | + (let [cur-index (:search/index @state) 58 | + container-el (. js/document getElementById "command-container") 59 | + next-el (nth (array-seq (.. container-el -children)) cur-index)] 60 | + (when (is-beyond-rect? next-el container-el) 61 | + (.. next-el (scrollIntoView (zero? cur-index) {:behavior "auto"})))))) 62 | 63 | ``` 64 | 65 | ### Deploying Hail the Wheel 66 | 67 | Coming from the JavaScript world, and always wanting to go for best in class, or the right way to do something I try to follow tutorials to do things. Just in case there are specifics I don't want to miss. And there are tutorials about anything and everything in JavaScript. I was a little bit put off by what I was reading about ClojureScript deployment. It was all so specific. Then it hit me, I don't have a backend. Why am I bothering with anything. I can create just create a prod version and deploy that, it's a static website. A `lein garden once && lein prod` is enough and then I can just deploy the files found in `/resources/public`. Here is the link, for now, but it's not ready for prime time yet: https://vercel.com/alaq/hail-the-wheel/pqv4vutt7 68 | 69 | I also did some more bike-shedding on the wheel, it's going to look :chef_kiss_emoji, I am in absolutely no rush! 70 | -------------------------------------------------------------------------------- /posts/2020-07-25.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 5 Day 6 (34/35) 2 | 3 | ## Expectations 4 | 5 | Sleepy Saturday, low expectations and second to last day of ClojureFam. I do have code to fix the scrolling issue in the slash commands in Athena (see [yesterday's update](./2020-07-25.md)). The expectation is to finish the code for it and open the pull request. 6 | 7 | ## What I Did 8 | 9 | I fixed the out of bounds issue that was actually caused by the function referencing a nil variable. The linter didn't complain because the variable was actually being declared in the scope but it was for another case in the `cond` that had nothing to do with slash commands. 10 | 11 | I also removed the element selection using an id, and instead used a (I think) overly complicated combination of parent and siblings selectors. This is the end result: 12 | 13 | ```diff 14 | (= :up direction) (do 15 | (.. e preventDefault) 16 | (swap! state update :search/index #(dec (if (zero? %) (count slash-options) %))) 17 | + (let [cur-index (:search/index @state) 18 | container-el (.. e -target -parentNode -parentNode -nextSibling -firstChild) 19 | next-el (nth (array-seq (.. container-el -children)) cur-index)] 20 | (when (is-beyond-rect? next-el (.. container-el -parentNode)) 21 | (.. next-el (scrollIntoView false {:behavior "auto"}))))) 22 | ``` 23 | 24 | ## Takeaways 25 | 26 | I feel like I'm learning a lot more by writing actual code, for production rather than little algorithms on the side. Don't get me wrong, it was good in the beginning but this is the current best way for me to learn. 27 | This is also the second to last day of ClojureFam, on a week end, and I feel the fatigue. I may have to take a break next week, for a couple of days maybe read a book. But the experience has been amazing and I'll be sure to continue in the next weeks. I hope to make a lot more contributions to the Athens codebase. 28 | -------------------------------------------------------------------------------- /posts/2020-07-26.md: -------------------------------------------------------------------------------- 1 | # Learning Clojure in Public - Week 5 Day 7 (35/35) 2 | 3 | ## Expectations 4 | 5 | Last day of ClojureFam. I started very late so I don't expect to get a lot done. 6 | 7 | ## What I Did 8 | 9 | ### Attempting to display the filter element by clicking on the filter icon in the linked references 10 | 11 | I did find the button in `views/node_page.cljs`, which is the first step: 12 | 13 | ```diff 14 | @@ -289,7 +308,7 @@ 15 | [:h4 (use-style references-heading-style) 16 | [(r/adapt-react-class mui-icons/Link)] 17 | [:span linked-or-unlinked] 18 | - [button {:disabled true} [(r/adapt-react-class mui-icons/FilterList)]]] 19 | + [button {:on-click (fn [] (js/alert "hello"))} [(r/adapt-react-class mui-icons/FilterList)]]] 20 | [:div (use-style references-list-style) 21 | (doall 22 | (for [[group-title group] refs] 23 | 24 | ``` 25 | 26 | However, I have not yet been able to show the element itself. Displaying it not on demand would be easy, as it is done in `devcards/filters.cljs`: 27 | 28 | ```clojure 29 | (defcard-rg Filters 30 | [:div (use-style devcard-wrapper) 31 | [filters-el "((some-uid))" items]]) 32 | ``` 33 | 34 | ### 4Clojure's 44th problem 35 | 36 | So I was stuck on this filter problem so I decided to try my hand at something else I have been stuck on, and it's 4Clojure's 44th problem. I had the case with positive rotation down, but had issues with how to use `mod` to make the negative rotations work. 37 | 38 | The problem was to rotate a sequence in any direction, by a number `n`. Intuitively the rotation, for positive `n`s is the following: 39 | 40 | ```clojure 41 | (fn my-rotate [n coll] 42 | (concat (drop n coll) (take n coll))) 43 | ``` 44 | 45 | I had been trying to come up with complex solutions, and if statement to solve this, when the only thing I actually need is for `n` to be 3 when it started as -2. I just tried out, without even thinking about it, `(mod -2 5)` and got 3, which is why I tried the following, which got me to the right answer: 46 | 47 | ```diff 48 | diff 49 | (fn my-rotate [n coll] 50 | + (let [n (mod n (count coll))] 51 | (concat (drop n coll) (take n coll)))) 52 | ``` 53 | 54 | ## Takeaways 55 | 56 | It's been an amazing experience and I wish I could finish it with a bang but there is still so much to do, so much to learn. I do plan to write my thoughts about the program, what I liked, what I would change but I still have to get my thought together. It has felt like a sprint and I need time to reflect. 57 | 58 | Still sad I didn't get more done today, but I got started very late and have to get ready for the week. It's still good that I was able to get some work done. I will probably take it easy this week but will still try to get a few things done. Some contributions to Athens, as well as all the things I didn't have time to do while doing the program (correct setup of text editor, etc.). 59 | -------------------------------------------------------------------------------- /posts/images/Clojure_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /posts/images/Screenshot from 2020-06-19 00-04-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaq/learning-clojure-in-public/27d11c1d8cad289b3fb9278082812a019d42b8d8/posts/images/Screenshot from 2020-06-19 00-04-04.png -------------------------------------------------------------------------------- /posts/images/athens-tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaq/learning-clojure-in-public/27d11c1d8cad289b3fb9278082812a019d42b8d8/posts/images/athens-tweet.png -------------------------------------------------------------------------------- /posts/images/lisankie-inspiration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaq/learning-clojure-in-public/27d11c1d8cad289b3fb9278082812a019d42b8d8/posts/images/lisankie-inspiration.png -------------------------------------------------------------------------------- /posts/images/open-source-lambda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaq/learning-clojure-in-public/27d11c1d8cad289b3fb9278082812a019d42b8d8/posts/images/open-source-lambda.png -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | 2 | (defproject letmerepl "0.1.0-SNAPSHOT" 3 | :description "FIXME: write description" 4 | :url "http://example.com/FIXME" 5 | :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 6 | :url "https://www.eclipse.org/legal/epl-2.0/"} 7 | :dependencies [[org.clojure/clojure "1.10.1"] [org.clojure/core.async "0.1.346.0-17112a-alpha"]] 8 | :main ^:skip-aot playsync.core 9 | :target-path "target/%s" 10 | :profiles {:uberjar {:aot :all}}) -------------------------------------------------------------------------------- /target/default/classes/META-INF/maven/letmerepl/letmerepl/pom.properties: -------------------------------------------------------------------------------- 1 | #Leiningen 2 | #Mon Jul 27 01:29:37 EDT 2020 3 | groupId=letmerepl 4 | artifactId=letmerepl 5 | version=0.1.0-SNAPSHOT 6 | revision=cb562b2a26413d7022f3803ffef468b01663b6d5 7 | -------------------------------------------------------------------------------- /target/default/stale/leiningen.core.classpath.extract-native-dependencies: -------------------------------------------------------------------------------- 1 | [{:dependencies {args4j {:vsn "2.0.26", :native-prefix nil}, org.clojure/data.json {:vsn "0.2.6", :native-prefix nil}, org.clojure/clojure {:vsn "1.10.1", :native-prefix nil}, javax.activation/javax.activation-api {:vsn "1.2.0", :native-prefix nil}, org.clojure/tools.analyzer {:vsn "0.1.0-beta12", :native-prefix nil}, org.clojure/core.specs.alpha {:vsn "0.2.44", :native-prefix nil}, org.clojure/spec.alpha {:vsn "0.2.176", :native-prefix nil}, org.clojure/tools.analyzer.jvm {:vsn "0.1.0-beta12", :native-prefix nil}, org.clojure/google-closure-library {:vsn "0.0-20151016-61277aea", :native-prefix nil}, org.clojure/clojurescript {:vsn "1.8.51", :native-prefix nil}, org.mozilla/rhino {:vsn "1.7R5", :native-prefix nil}, org.clojure/google-closure-library-third-party {:vsn "0.0-20151016-61277aea", :native-prefix nil}, com.google.javascript/closure-compiler-externs {:vsn "v20160315", :native-prefix nil}, clojure-complete {:vsn "0.2.5", :native-prefix nil}, com.google.guava/guava {:vsn "19.0", :native-prefix nil}, com.google.javascript/closure-compiler {:vsn "v20160315", :native-prefix nil}, cider/cider-nrepl {:vsn "0.25.0", :native-prefix nil}, org.clojure/tools.reader {:vsn "1.0.0-beta1", :native-prefix nil}, nrepl {:vsn "0.7.0", :native-prefix nil}, com.google.protobuf/protobuf-java {:vsn "2.5.0", :native-prefix nil}, org.clojure/core.memoize {:vsn "0.5.6", :native-prefix nil}, org.clojure/data.priority-map {:vsn "0.0.2", :native-prefix nil}, cider/piggieback {:vsn "0.5.0", :native-prefix nil}, com.google.code.findbugs/jsr305 {:vsn "1.3.9", :native-prefix nil}, org.clojure/core.cache {:vsn "0.6.3", :native-prefix nil}, refactor-nrepl {:vsn "2.5.0", :native-prefix nil}, org.ow2.asm/asm-all {:vsn "4.1", :native-prefix nil}, org.clojure/core.async {:vsn "0.1.346.0-17112a-alpha", :native-prefix nil}, javax.xml.bind/jaxb-api {:vsn "2.3.1", :native-prefix nil}, com.google.code.gson/gson {:vsn "2.2.4", :native-prefix nil}}, :native-path "target/default/native"} {:native-path "target/default/native", :dependencies {args4j {:vsn "2.0.26", :native-prefix nil, :native? false}, org.clojure/data.json {:vsn "0.2.6", :native-prefix nil, :native? false}, org.clojure/clojure {:vsn "1.10.1", :native-prefix nil, :native? false}, javax.activation/javax.activation-api {:vsn "1.2.0", :native-prefix nil, :native? false}, org.clojure/tools.analyzer {:vsn "0.1.0-beta12", :native-prefix nil, :native? false}, org.clojure/core.specs.alpha {:vsn "0.2.44", :native-prefix nil, :native? false}, org.clojure/spec.alpha {:vsn "0.2.176", :native-prefix nil, :native? false}, org.clojure/tools.analyzer.jvm {:vsn "0.1.0-beta12", :native-prefix nil, :native? false}, org.clojure/google-closure-library {:vsn "0.0-20151016-61277aea", :native-prefix nil, :native? false}, org.clojure/clojurescript {:vsn "1.8.51", :native-prefix nil, :native? false}, org.mozilla/rhino {:vsn "1.7R5", :native-prefix nil, :native? false}, org.clojure/google-closure-library-third-party {:vsn "0.0-20151016-61277aea", :native-prefix nil, :native? false}, com.google.javascript/closure-compiler-externs {:vsn "v20160315", :native-prefix nil, :native? false}, clojure-complete {:vsn "0.2.5", :native-prefix nil, :native? false}, com.google.guava/guava {:vsn "19.0", :native-prefix nil, :native? false}, com.google.javascript/closure-compiler {:vsn "v20160315", :native-prefix nil, :native? false}, cider/cider-nrepl {:vsn "0.25.0", :native-prefix nil, :native? false}, org.clojure/tools.reader {:vsn "1.0.0-beta1", :native-prefix nil, :native? false}, nrepl {:vsn "0.7.0", :native-prefix nil, :native? false}, com.google.protobuf/protobuf-java {:vsn "2.5.0", :native-prefix nil, :native? false}, org.clojure/core.memoize {:vsn "0.5.6", :native-prefix nil, :native? false}, org.clojure/data.priority-map {:vsn "0.0.2", :native-prefix nil, :native? false}, cider/piggieback {:vsn "0.5.0", :native-prefix nil, :native? false}, com.google.code.findbugs/jsr305 {:vsn "1.3.9", :native-prefix nil, :native? false}, org.clojure/core.cache {:vsn "0.6.3", :native-prefix nil, :native? false}, refactor-nrepl {:vsn "2.5.0", :native-prefix nil, :native? false}, org.ow2.asm/asm-all {:vsn "4.1", :native-prefix nil, :native? false}, org.clojure/core.async {:vsn "0.1.346.0-17112a-alpha", :native-prefix nil, :native? false}, javax.xml.bind/jaxb-api {:vsn "2.3.1", :native-prefix nil, :native? false}, com.google.code.gson/gson {:vsn "2.2.4", :native-prefix nil, :native? false}}}] --------------------------------------------------------------------------------