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 |
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 | 
28 |
29 | Ideas from our [Figma](https://www.figma.com/file/iCXP6z7H5IAQ6xyFr5AbZ7/Product-Design-Sandbox?node-id=524%3A1389)
30 |
31 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------
/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}}}]
--------------------------------------------------------------------------------