Mercurial > repos > other > SevenLanguagesInSevenWeeks
annotate 6-Clojure/README.txt @ 89:7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sat, 15 Jun 2019 21:07:27 +0100 |
parents | 920b50be0fe5 |
children |
rev | line source |
---|---|
79
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
1 Install Clojure with "curl -O https://download.clojure.org/install/linux-install-1.10.0.442.sh; chmod +x linux-install-1.10.0.442.sh; sudo ./linux-install-1.10.0.442.sh" (it's a downloader that downloads the real package) |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
2 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
3 Clojure has a repl (clj), which needs rlwrap ("sudo zypper in rlwrap"). This then downloads some MORE files from Maven. Don't you just LOVE the Java ecosystem?! |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
4 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
5 Clojure is a Lisp dialect. It feels a little like Prolog in that it's logic and brackets galore, but Prolog is about predicates and pattern matching whereas Lisp has more of an imperative language with odd syntax |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
6 but sensible things like "if" statements. It uses strong, dynamic typing. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
7 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
8 Running commands at the repl and prints output *and* the return value (which can be "nil"). |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
9 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
10 Commands are prefix, not infix: |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
11 "Normal" languages: 1 + 1 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
12 Clojure: (+ 1 1) |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
13 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
14 Division of integers creates a (normalised) "ratio" type to maintain accuracy: |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
15 (/ 2 4) → 1/2 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
16 (/ 2.0 4) → 0.5 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
17 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
18 Prefix does have its advantages, though - it's more like a function and can have variable numbers of parameters: |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
19 (+ 2 2 2 2) → 8 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
20 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
21 And it can be used to check order: |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
22 (< 1 2 3) → true |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
23 (< 1 3 2 4) → false |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
24 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
25 Strings are double-quoted, but characters can be escaped with a backslash outside quotes to become a string. When done with "str" it makes a string: |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
26 (str \h \e \l \l \o) → "hello" |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
27 This seems like it would rarely be useful. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
28 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
29 Strings and characters are different (just like in Java) |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
30 (= "a" \a) → false |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
31 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
32 Clojure can pass code as arguments, e.g.: |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
33 (if true (println "It's true!")) |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
34 but that's quite normal for functional languages. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
35 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
36 The "if" function is basically "if <condition> <true>[ <false>]". |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
37 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
38 Note: 0 and "" are true but nil is false, so only false and the null value are falsey. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
39 |
81
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
40 Clojure has four main data structures - lists (for code), vectors (for data), sets and maps. These are types of sequence. Other things may also be sequences (strings, file system structures, etc). |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
41 Sequences can then be operated on with "first", "last", "rest" and "cons" (construct from head and tail) functions. |
79
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
42 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
43 You can't build a list Python style with "(1 2 3)". You need either "(list 1 2 3)" or the odd syntax "'(1 2 3)". |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
44 There are also functions like "nth" (nth <list> <0-based index>) |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
45 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
46 Vectors use sequare brackets. They're like a list, but you can implicitly index them (i.e. you don't need "nth") by doing ([vector] pos) because they're treated as functions. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
47 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
48 first, last and rest can be used for pattern matching. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
49 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
50 Sets use hash followed by squiggly brackets (which is easily confused with a map). Sorted sets use "(sorted-set <values> …)". |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
51 Manipulating sets is less clean - (clojure.set/union <set1> <set2>) for unioning sets and (clojure.set/difference <set1> <set2>) for difference. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
52 Sets are also functions and so you can check if an item is in a set with (#{set} <value>). Returns "nil" if not in the set, else returns the value. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
53 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
54 Maps are defined with squiggly brackets and have implicit key-value positions, so you can write"{:chewie :wookie :lea :human}" for keys :chewie and :lea with values :wookie and :human. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
55 You can also put in commas, though. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
56 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
57 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
58 ":text" appears to be a unique identifier ("keyword"), but the book doesn't mention this until three pages after it started using it. They're like Ruby symbols and Prolog/Erland atoms. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
59 Symbols point to something else and keywords point to themselves (although it isn't made clear what that means). |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
60 Keywords are also functions, so (map key) and (key map) are equivalent. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
61 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
62 Maps can be merged with "merge" or with "merge-with", which takes a function to calculate the merged value. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
63 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
64 Variables are assigned to with "(def <variable-name> <value>)". Functions are defined with "(defn <func-name> [<params list>] (<body>))". |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
65 There's also an optional docstring after <func-name>. Docs can be read with "(doc <func-name>)". |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
66 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
67 Function arguments can be quite complex and use pattern matching, e.g. getting the second value in a vector with: |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
68 (defn line-end [[_ second]] second) |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
69 which takes a single arg (the vector "[_ second]") and the body just returns "second". Clojure calls this "destructuring". It ignores any additional values (e.g. "[_ c]" matches "[1 2 3 4 5]" and puts "2" in c and ignores the trailing 3,4,5). |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
70 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
71 Where "def" is used for long-term variables (which are created in user-space - the return result is #'user/<def-name>), "let" is used for local variables, which is useful within function definitions, e.g.: |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
72 (defn centre_space [board] (let [[_ [_ c]] board] c)) |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
73 which is a function with one argument (board) where the function body uses "let" with a vector containing a pattern to match and a variable to match against, and then a function that uses that vector. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
74 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
75 There are also anonymous functions that use "(fn [args] (body))" - like a defn, but without a name. Alternatively, "#(function-body-with-%-as-a-variable)" defines an anonymous "reader macro" function with % bound to each value. |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
76 (map (fn [w] (* 2 (count w))) my_list) |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
77 is equivalent to: |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
78 (map #(* 2 (count %)) my_list) |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
79 |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
80 As well as the map function, there's "apply" and "filter". |
29025305bbb7
Add notes and exercises from Day 1 of Clojure
IBBoard <dev@ibboard.co.uk>
parents:
diff
changeset
|
81 |
81
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
82 Also, some helper functions exist, like "odd?". Terminating in a question mark seems to be the Clojure approach to predicates. Sometimes you end up with multiple question marks: |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
83 |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
84 (every? number? [1 2 3 :four]) |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
85 ;false |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
86 |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
87 not-every? and not-any? both have question marks as well. But not everything is question marked: |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
88 |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
89 (some nil? [1 2 nil]) |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
90 ; true |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
91 |
82 | 92 This seems oddly inconsistent. The footnote explains that it's because "some returns the first value that is not nil or false", so "nil?" return false for 1 and 2 and then return true for nil and so some returns true. |
93 With other "some" filters, like "(some odd? …)", it works because nil (if it doesn't find anything) is falsey and numbers (if it finds an odd one) are truthy. | |
94 | |
81
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
95 It's not a predicate because "(some nil? [1 2]) returns nil, not false. |
80
a83309cdf5d3
Add details of Clojure's loop recursion approach
IBBoard <dev@ibboard.co.uk>
parents:
79
diff
changeset
|
96 |
a83309cdf5d3
Add details of Clojure's loop recursion approach
IBBoard <dev@ibboard.co.uk>
parents:
79
diff
changeset
|
97 Functional languages do lazy tail recursion. Unless they're Clojure, because the JVM doesn't support it. Clojure does it with a "loop" and a "recur" function. "loop" takes x and y with initial values and a function to call. See loop_recur.clj. |
a83309cdf5d3
Add details of Clojure's loop recursion approach
IBBoard <dev@ibboard.co.uk>
parents:
79
diff
changeset
|
98 |
81
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
99 For loops take the form (for [val collection<, val2 collection2<, …>>] (body)), which is a bit like "for X in Collection", but doesn't look like it at first. |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
100 Multiple val/collection pairs give nested for loops (so every val with every val2). |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
101 |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
102 But then for loops can take a ":when" test keyword (or :let or :while). Which is odd, because :xxx has only ever been a user atom, but this one already has meaning. And they can be mixed anywhere in the parameters and Clojure knows what to do. |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
103 |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
104 Reduce is more familiar: (reduce func list) |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
105 |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
106 (reduce + [1 2 3 4]) ; sums |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
107 (reduce * [1 2 3 4 5]) ; factorial |
0f57e5c2ae82
Add notes on sequences in Clojure
IBBoard <dev@ibboard.co.uk>
parents:
80
diff
changeset
|
108 |
82 | 109 As well as sorting a list with (sort list) you can use a custom funcion with (sort-by function list) where "function" takes a single parameter and generates a key. |
110 | |
111 Infinite sequences can be built with (repeat obj), (cycle [list]) and (iterate func start_obj). (take count list) takes the number of items that you want from a potentially infinite sequence (which is lazily evaluated). There's also a (drop count list) function to skip items. | |
112 If you don't like the "inside function happens first" Lisp-like function order then the slightly bizarre "->>" function (left-to-right operator) applies a number of functions in order, e.g.: | |
113 | |
114 (->> [:later :rinse : repeat] (cycle) (drop 2) (take 5)) | |
115 ; equivalent to (take 5 (drop 2 (cycle [:later :rinse : repeat]))) | |
116 | |
117 You can also (interpose obj list) to insert something between each item and (interleave list list) to interleave two lists. | |
118 | |
83
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
119 The book seems to use "(take n (iterate inc m))" rather than "(range m n+1)" but doesn't say why. Range is lazy, as is iterate. The only benefit to iterate is if you're doing an 'infinite' list with a high start, as it starts high rather than spending time generating and dropping. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
120 |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
121 Clojure doesn't have types/classes and interfaces. It has `defrecord` (types) and `defprotocol` (interfaces or contracts). This wasn't explained particularly well in the book (too much shown without explanation, or with explanation a page later) so bearings.clj reworks it with more documentation (and some better implementations). |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
122 |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
123 Clojure agressively executes code, so an "unless" function won't work because the function will always be execute *before* it checks whether the "unless" condition is false. This can apparently be fixed with macros, which are expanded before execution. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
124 Three paragraphs later it explains(?) that you use a macro to turn code into lists and that macro substitution doesn't evaluate the contents. But you've got to quote your functions and build lists. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
125 ";" for comments, "'" for quotes and "#" for anonymous functions are pre-existing "reader macros". |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
126 "Data as code" (your data can be executed) is powerful, but it looks messy and confusing. And it's still not clear why "code" is all evaluated straight awaybut "code put in by macro in first parsing pass" is not. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
127 |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
128 (when boolean expression1 expression2 expression…) is a macro that effectively becomes (if boolean (do expression1…)) - https://www.braveclojure.com/writing-macros/ - but is actually constructed as (list 'if test (cons 'do body)). "Clojure for the Brave and True" makes this a *little* clearer. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
129 |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
130 Clojure does concurrency without locking and without actors. It uses "software transactional memory" (STM). You create a (ref thing) to a thing and then pass it around (def myvar (ref "refd string")). myvar then holds the ref and (deref myvar) or @myvar gets the original value back (note: deref does not remove the reference, it just follows it to the value). |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
131 Changes to the <Ref> object need to be in a transaction, such as (dosync …). (alter myref func args) calls func with @myref and args. (ref-set myref new_value) swaps the value. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
132 |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
133 Thread safety without transactions comes from (atom thing_to_atom) rather than (ref …) but is used in a similar way. You can replace an atom's value using (reset! myatom value), but it is more Clojure-y (Clojonic?) to use (swap! myatom func extra_args). Note that "atom" doesn't have to mean "simple" - maps can be atoms, but atom gives you the ability to edit a variable with thread safety. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
134 |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
135 Agents are like atoms but with async updates. You (send agent func) to update their value. If the update is slow then @agent will return its 'old' value until the update is complete, but the update will be transactional. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
136 The book says "If you think you want the latest value of something, you have already failed". Presumably that's where you should use atoms, although it doesn't tell you that. Applying functions to the value through (swap! …) means it implicitly gets the latest value at execution time. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
137 |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
138 From https://stackoverflow.com/a/9136699/283242: |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
139 Refs are for Coordinated Synchronous access to "Many Identities". |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
140 Atoms are for Uncoordinated synchronous access to a single Identity. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
141 Agents are for Uncoordinated asynchronous access to a single Identity. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
142 Vars are for thread local isolated identities with a shared default value. |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
143 |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
144 Futures are the same as in other languages. (future <some sequence of expressions>) creates a future that returns the last value when dereferenced with @my_future and the app blocks (unlike actors, which need (await …) to block, and that's only until your changes are complete) |
eccc649d49a2
Add Day 2 and Day 3 notes and example code
IBBoard <dev@ibboard.co.uk>
parents:
82
diff
changeset
|
145 |
84
920b50be0fe5
Add notes (and failed code) for blocking queue
IBBoard <dev@ibboard.co.uk>
parents:
83
diff
changeset
|
146 Clojure can also call all the standard Java methods on a Java object using (.javaMethodName object), e.g. (.toUpperCase "Using Clojure") |
920b50be0fe5
Add notes (and failed code) for blocking queue
IBBoard <dev@ibboard.co.uk>
parents:
83
diff
changeset
|
147 |
920b50be0fe5
Add notes (and failed code) for blocking queue
IBBoard <dev@ibboard.co.uk>
parents:
83
diff
changeset
|
148 Day 3 exercise says to find a queue that blocks when it is empty. The best I can find is a wrapper on java.util.concurrent.LinkedBlockingDeque (https://gist.github.com/mjg123/1305115/72434dd5da89e5a83c91facb0b7687d6b7e66836). I've tried creating one but can't get the logic right. |
920b50be0fe5
Add notes (and failed code) for blocking queue
IBBoard <dev@ibboard.co.uk>
parents:
83
diff
changeset
|
149 It *feels* like an atom job, then an agent to do an async push, but then you're just adding an empty check and Thread/sleep to a loop. Or you use actors with (await …) rather than atoms, but await only waits for changes in your thread, which is of minimal use with a blocking queue. |