Skip to content

Latest commit

 

History

History
205 lines (150 loc) · 6.35 KB

2020-07-09.md

File metadata and controls

205 lines (150 loc) · 6.35 KB

Learning Clojure in Public - Week 3 Day 4 (18/35)

What I learned

Evaluation in Clojure

You can send your code straight to the evaluator with eval.

Clojure is homoiconic: it represents abstract syntax trees using lists, and you write textual representations of lists when you write Clojure code.

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:

(read-string "'(a b c)")
; => (quote (a b c))

Macros

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:

(defmacro ignore-last-operand
  [function-call]
  (butlast function-call))

(ignore-last-operand (+ 1 2 10))
; => 3

;; This will not print anything
(ignore-last-operand (+ 1 2 (println "look at me!!!")))
; => 3

The data structure returned by a function is not evaluated, but the data structure returned by a macro is.

macroexpand lets you see what datastructure is returned by the macro:

(macroexpand '(ignore-last-operand (+ 1 2 10)))
; => (+ 1 2)

Threading

You can reverse the order in which function calls are written with ->:

(defn read-resource
  "Read a resource into a string"
  [path]
  (read-string (slurp (clojure.java.io/resource path))))

; is equivalent to

(defn read-resource
  [path]
  (-> path
      clojure.java.io/resource
      slurp
      read-string))

Brave Clojure Chapter 7 Exercises

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.

(eval (list (read-string "println") '(str "Adrien " "Battlestar Galactica")))

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.

This was similar to 4Clojure problem 135, the only difference is it is taking a list and not variable arity:

(defn infix
  [xs]
  (reduce
    (fn [acc e]
      (if (fn? e)
        (partial e acc)
        (acc e)))
    (partial + 0) xs))

Reagent

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".

A Reagent component is written up like this:

(defn simple-component []
  [:div
   [:p "I am a component!"]
   [:p.someclass
    "I have " [:strong "bold"]
    [:span {:style {:color "red"}} " and red "] "text."]])

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].

A component takes arguments like a function:

(defn hello-component [name]
  [:p "Hello, " name "!"])

(defn say-hello []
  [hello-component "world"])

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:

(defn lister [items]
  [:ul
   (for [item items]
     ^{:key item} [:li "Item " item])])

(defn lister-user []
  [:div
   "Here is a list:"
   [lister (range 3)]])

Here ^{:key item} is used like in React to identify the element.

State in Reagent

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.

(ns example
  (:require [reagent.core :as r]))
(def click-count (r/atom 0))

(defn counting-component []
  [:div
   "The atom " [:code "click-count"] " has value: "
   @click-count ". "
   [:input {:type "button" :value "Click me!"
            :on-click #(swap! click-count inc)}]])

;; Or even within a component
(defn timer-component []
  (let [seconds-elapsed (r/atom 0)]
    (fn []
      (js/setTimeout #(swap! seconds-elapsed inc) 1000)
      [:div
       "Seconds Elapsed: " @seconds-elapsed])))

Then you can pass the atom around for other components to benefit from the state:

(ns example
  (:require [reagent.core :as r]))
(defn atom-input [value]
  [:input {:type "text"
           :value @value
           :on-change #(reset! value (-> % .-target .-value))}])

(defn shared-state []
  (let [val (r/atom "foo")]
    (fn []
      [:div
       [:p "The value is now: " @val]
       [:p "Change it here: " [atom-input val]]])))

Attaching the React app to a dom node

You can (and should!) attach the component to a node in your html and you can do so like this:

(ns example
  (:require [reagent.core :as r]))
(defn simple-component []
  [:div
   [:p "I am a component!"]
   [:p.someclass
    "I have " [:strong "bold"]
    [:span {:style {:color "red"}} " and red "] "text."]])

(defn render-simple []
  (rdom/render
    [simple-component]
    (.-body js/document)))

Other things to note about Reagent

Takeaways

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.

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.

Let's end the day with a tally of what I completed:

  • Chapter 7 of Clojure for the Brave and True
  • The complete introduction to Reagent, the minimalistic React for ClojureScript
  • 2 4clojure problems