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