changeset 83:eccc649d49a2

Add Day 2 and Day 3 notes and example code Clojure STILL doesn't make much sense in lots of places
author IBBoard <dev@ibboard.co.uk>
date Sat, 08 Jun 2019 21:23:27 +0100
parents cf7182bca068
children 920b50be0fe5
files 6-Clojure/README.txt 6-Clojure/atomcache.clj 6-Clojure/bearings.clj 6-Clojure/fibonaci.clj 6-Clojure/macro.clj
diffstat 5 files changed, 104 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/6-Clojure/README.txt	Sat Jun 08 14:00:25 2019 +0100
+++ b/6-Clojure/README.txt	Sat Jun 08 21:23:27 2019 +0100
@@ -116,4 +116,31 @@
 
 You can also (interpose obj list) to insert something between each item and (interleave list list) to interleave two lists.
 
-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.
\ No newline at end of file
+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.
+
+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).
+
+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.
+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.
+";" for comments, "'" for quotes and "#" for anonymous functions are pre-existing "reader macros".
+"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.
+
+(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.
+
+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).
+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.
+
+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.
+
+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.
+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.
+
+From https://stackoverflow.com/a/9136699/283242:
+    Refs are for Coordinated Synchronous access to "Many Identities".
+    Atoms are for Uncoordinated synchronous access to a single Identity.
+    Agents are for Uncoordinated asynchronous access to a single Identity.
+    Vars are for thread local isolated identities with a shared default value.
+
+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)
+
+Clojure can also call all the standard Java methods on a Java object using (.javaMethodName object), e.g. (.toUpperCase "Using Clojure")
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/6-Clojure/atomcache.clj	Sat Jun 08 21:23:27 2019 +0100
@@ -0,0 +1,22 @@
+(defn create_price_list
+    []
+    (atom {}))
+
+; We can't use get because it's part of clojure.core
+(defn get_price
+    [prices name]
+    (@prices name))
+
+(defn set_price
+    ([prices new_price_map]
+        (swap! prices merge new_price_map))
+    ([prices name price]
+        (swap! prices assoc name price)))
+
+(def my_shares (create_price_list))
+(set_price my_shares :GW 299)
+(set_price my_shares :BT 1563)
+(set_price my_shares :ROYALMAIL 29)
+(println (get_price my_shares :BT))
+(println (get_price my_shares :FOO))
+(println my_shares)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/6-Clojure/bearings.clj	Sat Jun 08 21:23:27 2019 +0100
@@ -0,0 +1,33 @@
+; Based on Compass
+(def directions [:north :east :south :west])
+
+; Define a "fixed movement" protocol - "m" is effectively "this"
+(defprotocol FixedMovement
+    (direction [m]) ; Find your current direction
+    (left [m]) ; turn left one position
+    (right [m])) ; turn right one position
+
+; Define an implementation with a single field (not a parameter - although it's defined the same)
+(defrecord CompassBearings [bearing]
+    ; Specify which protocol we're implementing
+    FixedMovement
+    ; And define the methods
+    (direction [_] (directions bearing)) ; Look-up our index in the list
+    ; Clojure uses immutable values, so these functions create new CompassBearing objects
+    (left [_] (CompassBearings. (mod (- bearing 1) (count directions)))) ; Create a new instance (ClassName.) with a value from rotating one slot left
+    (right [_] (CompassBearings. (mod (+ bearing 1) (count directions))))
+    ; And we can specify more
+    Object
+    (toString [this] (str "[" (direction this) "]")) ; Presumably we could also call (directions bearing) directly.
+    ; We have to pass something to (direction) because we're doing functions and so can't do "obj.function()" to keep the reference to what we're getting directions of.
+    )
+
+; Define a thing that has bearings - e.g. an actor in a scene
+; It'd be nice if we could do an automated default so that we're not passing in magic numbers
+(def actor (CompassBearings. 0))
+(println actor) ; Prints default internal format
+(println (:bearing actor)) ; Access a field using the keyword as a function. Because keywords with meaning happen EVERYWHERE in Clojure!
+(println (str actor)) ; Uses our toString method
+(println (str (left actor)))
+(println (str (right (right actor))))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/6-Clojure/fibonaci.clj	Sat Jun 08 21:23:27 2019 +0100
@@ -0,0 +1,14 @@
+; For a pair of (assumed) consecutive (assumed) Fibonacci numbers and generates the next pair
+(defn fib-pair [[a b]] ; I *think* this a vector and not just two parameters so that we can iterate (because you can't return two values)
+    [b (+ a b)])
+
+(print (fib-pair [3 5]))
+
+(print 
+    (take 5
+        (map first (iterate fib-pair [1 1]))))
+
+(print 
+    (nth 
+        (map first (iterate fib-pair [1 1]))
+        50)) ;The book says 500th, but even 100th gives java.lang.ArithmeticException: integer overflow!
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/6-Clojure/macro.clj	Sat Jun 08 21:23:27 2019 +0100
@@ -0,0 +1,7 @@
+(defmacro unless [test body]
+    (list 'if (list 'not test) body))
+
+(macroexpand '(unless condition body))
+
+(unless true (println "Should not print"))
+(unless false (println "Should print"))
\ No newline at end of file