Mercurial > repos > other > SevenLanguagesInSevenWeeks
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