changeset 79:29025305bbb7

Add notes and exercises from Day 1 of Clojure
author IBBoard <dev@ibboard.co.uk>
date Sat, 01 Jun 2019 20:10:59 +0100
parents 75dbcd30dee5
children a83309cdf5d3
files 6-Clojure/README.txt 6-Clojure/big.clj 6-Clojure/collection-type.clj
diffstat 3 files changed, 168 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/6-Clojure/README.txt	Sat Jun 01 20:10:59 2019 +0100
@@ -0,0 +1,82 @@
+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)
+
+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?!
+
+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
+but sensible things like "if" statements. It uses strong, dynamic typing.
+
+Running commands at the repl and prints output *and* the return value (which can be "nil").
+
+Commands are prefix, not infix:
+  "Normal" languages: 1 + 1
+  Clojure: (+ 1 1)
+
+Division of integers creates a (normalised) "ratio" type to maintain accuracy:
+  (/ 2 4) → 1/2
+  (/ 2.0 4) → 0.5
+
+Prefix does have its advantages, though - it's more like a function and can have variable numbers of parameters:
+  (+ 2 2 2 2) → 8
+
+And it can be used to check order:
+  (< 1 2 3) → true
+  (< 1 3 2 4) → false
+
+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:
+  (str \h \e \l \l \o) → "hello"
+This seems like it would rarely be useful.
+
+Strings and characters are different (just like in Java)
+  (= "a" \a) → false
+
+Clojure can pass code as arguments, e.g.:
+  (if true (println "It's true!"))
+but that's quite normal for functional languages.
+
+The "if" function is basically "if <condition> <true>[ <false>]".
+
+Note: 0 and "" are true but nil is false, so only false and the null value are falsey.
+
+Clojure has four main data structures - lists (for code), vectors (for data), sets and maps.
+
+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)".
+Lists can then be operated on with "first", "last", "rest" and "cons" (construct from head and tail) functions.
+There are also functions like "nth" (nth <list> <0-based index>)
+
+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.
+
+first, last and rest can be used for pattern matching.
+
+Sets use hash followed by squiggly brackets (which is easily confused with a map). Sorted sets use "(sorted-set <values> …)".
+Manipulating sets is less clean - (clojure.set/union <set1> <set2>) for unioning sets and (clojure.set/difference <set1> <set2>) for difference.
+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.
+
+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.
+You can also put in commas, though.
+
+
+":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.
+Symbols point to something else and keywords point to themselves (although it isn't made clear what that means).
+Keywords are also functions, so (map key) and (key map) are equivalent.
+
+Maps can be merged with "merge" or with "merge-with", which takes a function to calculate the merged value.
+
+Variables are assigned to with "(def <variable-name> <value>)". Functions are defined with "(defn <func-name> [<params list>] (<body>))".
+There's also an optional docstring after <func-name>. Docs can be read with "(doc <func-name>)".
+
+Function arguments can be quite complex and use pattern matching, e.g. getting the second value in a vector with:
+  (defn line-end [[_ second]] second)
+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).
+
+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.:
+  (defn centre_space [board] (let [[_ [_ c]] board] c))
+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.
+
+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.
+  (map (fn [w] (* 2 (count w))) my_list)
+is equivalent to:
+  (map #(* 2 (count %)) my_list)
+
+As well as the map function, there's "apply" and "filter".
+
+Also, some helper functions exist, like "odd?".
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/6-Clojure/big.clj	Sat Jun 01 20:10:59 2019 +0100
@@ -0,0 +1,6 @@
+(defn big [st n]
+    "Returns true if string 'st' is longer than 'n' characters"
+    (> (count st) n)
+)
+(println (big "hello" 5))
+(println (big "hello" 4))
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/6-Clojure/collection-type.clj	Sat Jun 01 20:10:59 2019 +0100
@@ -0,0 +1,80 @@
+(def classmap {
+        "class clojure.lang.PersistentArrayMap" :map,
+        "class clojure.lang.PersistentTreeMap" :map,
+        "class clojure.lang.PersistentList$EmptyList" :list,
+        "class clojure.lang.PersistentList" :list,
+        "class clojure.lang.PersistentVector" :vector,
+        "class clojure.lang.PersistentTreeSet" :set,
+        "class clojure.lang.PersistentHashSet" :set
+    })
+(defn collection-type [col]
+    "Writes :list, :map, set or :vector based on the collection's type"
+    ; There's probably a better way that accounts for inheritance,
+    ; but this works and uses the "map is a function with a lookup" approach!!
+    ;
+    ; We can't do this with classes directly, because of "java.lang.IllegalArgumentException: Duplicate key: (class ())"
+    ; because of a clash with (class []), even though they're different and (hash …) gives different answers!
+    (classmap (str (class col))))
+
+(println (collection-type ()))
+(println (collection-type (list 1 2 3)))
+(println (collection-type []))
+(println (collection-type [1 2 3]))
+(println (collection-type #{}))
+(println (collection-type (sorted-set)))
+(println (collection-type (sorted-map)))
+(println (collection-type {}))
+(println (collection-type "not a collection"))
+(println)
+
+
+(def classmap2 {
+        (class {}) :map,
+        (class (sorted-map)) :map,
+        (class ()) :list,
+        (class (list 1)) :list,
+        (class #{}) :set,
+        (class (sorted-set)) :set
+    })
+(defn collection-type2 [col]
+    "Writes :list, :map, set or :vector based on the collection's type"
+    ; There's probably a better way that accounts for inheritance,
+    ; but this works and uses the "map is a function with a lookup" approach!!
+    ;
+    ; The first IF works around the duplicate key issue (at least with this implementation of maps)
+    (if (= (class []) (class col)) :vector (classmap2 (class col))))
+
+(println (collection-type2 ()))
+(println (collection-type2 (list 1 2 3)))
+(println (collection-type2 []))
+(println (collection-type2 [1 2 3]))
+(println (collection-type2 #{}))
+(println (collection-type2 (sorted-set)))
+(println (collection-type2 (sorted-map)))
+(println (collection-type2 {}))
+(println (collection-type2 "not a collection"))
+(println)
+
+; Use (bases <class>) to find the parent classes
+(defn collection-type3 [col]
+    "Writes :list, :map, set or :vector based on the collection's type"
+    (let [col_class (class col)] 
+        (if (isa? col_class clojure.lang.IPersistentList) :list
+            (if (isa? col_class clojure.lang.APersistentVector) :vector
+                (if (isa? col_class clojure.lang.APersistentMap) :map
+                    (if (isa? col_class clojure.lang.APersistentSet) :set)
+                )
+            )
+        )
+    )
+)
+
+(println (collection-type3 ()))
+(println (collection-type3 (list 1 2 3)))
+(println (collection-type3 []))
+(println (collection-type3 [1 2 3]))
+(println (collection-type3 #{}))
+(println (collection-type3 (sorted-set)))
+(println (collection-type3 (sorted-map)))
+(println (collection-type3 {}))
+(println (collection-type3 "not a collection"))
\ No newline at end of file