clojurecember

My attempt to learn at least a little bit of Clojure each day in December
git clone https://git.sr.ht/~jbauer/clojurecember
Log | Files | Refs | README | LICENSE

commit c1e8becf58643c596f2fc3f26c39beb8a3cd5f6b
parent 93f4841e77989a466653c025ccb253c8c728509a
Author: Jake Bauer <jbauer@paritybit.ca>
Date:   Sat,  3 Dec 2022 20:38:12 -0500

Third day

Diffstat:
MREADME.md | 4++++
Dday2.clj | 227-------------------------------------------------------------------------------
Afunctions.clj | 238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahashed-collections.clj | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asequential-collections.clj | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rday1.clj -> syntax.clj | 0
6 files changed, 550 insertions(+), 227 deletions(-)

diff --git a/README.md b/README.md @@ -11,3 +11,7 @@ Guide](https://www.clojure.org/guides/learn/syntax). ## DEC02 Continued following the Clojure Guide with [Functions](https://www.clojure.org/guides/learn/functions). + +## DEC03 + +Finished up with Functions in Clojure and moved on to [Sequential](https://www.clojure.org/guides/learn/sequential_colls) and [Hashed](https://www.clojure.org/guides/learn/hashed_colls) collections. diff --git a/day2.clj b/day2.clj @@ -1,227 +0,0 @@ -; CREATING FUNCTIONS - -; defn defines a named function -; name params body -; ----- ------ ------------------- -(defn greet [name] (str "Hello, " name) ) - -; Invoke a function with the name of the function like so: -(greet "humans.") - -; MULTI-ARITY FUNCTIONS - -; Different arities must all be defined in the same defn, a second defn -; overwrites the first. -; Each arity is a list ([param*] body*). One arity can invoke another. -(defn messenger - ([] (messenger "Hello world!")) - ([msg] (println msg)) - -(messenger) -; Hello world! - -(messenger "Hello humans!") -; Hello humans! - -; VARIADIC FUNCTIONS - -; Variable number of parameters, the beginning of the variable parameters is -; marked with &. The variable number of parameters are collected into a list. -(defn hello [greeting & who] - (println greeting who)) - -(hello "Hello" "world" "class") -; Hello (world class) - -; ANONYMOUS FUNCTIONS - -; Created with fn -; Because it's anonymous, it cannot be referred to later and is typically -; created at the point it's passed to another function. -(fn [message] (println message) ) - -; It might be useful to think of defn as a contraction of def and fn where fn -; defines a function and def assigns it to a name. - -; Shorter form for the fn anonymous function syntax #() -; Omits parameter list and names parameters based on their position -; % used for a single character -; %1, %2, %3, etc are used for multiple parameters -; %& used for remaining variadic parameters -#(+ 6 %) ; (fn [x] (+ 6 x)) -#(+ %1 %2) ; (fn [x y] (+ x y)) -#(println %1 %2 %&) ; (fn [x y & zs] (println x y zs)) - -; Special GOTCHA -; Anonymous function that takes an element and wraps it in a vector -; #([%]) is wrong, it makes the equivalent (fn [x] ([x])) which will wrap in a -; vector and try to invoke the vector with no arguments. Use this instead: -#(vector %) -; or this -(fn [x] [x]) -; or just this -vector - -; APPLYING FUNCTIONS -; apply invokes a function with 0 or more fixed arguments, and draws the rest of -; the needed arguments from a final sequence. The final argument _must_ be a -; sequence. -(apply f '(1 2 3 4)) ;; same as (f 1 2 3 4) -(apply f 1 '(2 3 4)) ;; same as (f 1 2 3 4) -(apply f 1 2 '(3 4)) ;; same as (f 1 2 3 4) -(apply f 1 2 3 '(4)) ;; same as (f 1 2 3 4) - -; apply is useful when arguments are given to you as a sequence but you must -; invoke the function with the values in the sequence. For example, use it to -; avoid writing this: -(defn plot [shape coords] ;; coords is [x y] - (plotxy shape (first coords) (second coords))) -; and instead write this: -(defn plot [shape coords] - (apply plotxy shape coords)) - -; LOCALS AND CLOSURES - -; let binds symbols to values in "lexical scope" -; bindings name is defined here -; ------------ ---------------------- -(let [name value] (code that uses name)) - -; Each let can define 0 or more bindings and can have 0 or more expressions in -; the body -( let [x 1 - y 2] - (+x y)) - -; fn special form creates a "closure" (it "closes over" the surrounding lexical -; scope and captures their values beyond that scope) - -(defn messenger-builder [greeting] - (fn [who] (println greeting who))) ; closes over greeting - -; greeting provided here, then goes out of scope -(def hello-er (messenger-builder "Hello")) - -; greeting value still available because hello-er is a closure -(hello-er "world!") -; Hello world! - -; JAVA INTEROP - -; Below is a summary of calling conventions for calling into Java from Clojure: -; Task Java Clojure -; Instantiation new Widget("foo") (Widget. "foo") -; Instance method rnd.nextInt() (.nextInt rnd) -; Instance field object.field (.-field object) -; Static method Math.sqrt(25) (Math/sqrt 25) -; Static field Math.PI Math/PI - -; Java methods are not Clojure functions -; Can't store them or pass them as arguments -; Can wrap them in functions when necessary - -; make a function to invoke .length on arg -(fn [obj] (.length obj)) -; same thing -#(.length %) - -; TEST YOUR KNOWLEDGE - -; 1. Define a function greet that takes no arguments and prints "Hello" - -(defn greet [] - (println "Hello")) - -; 2. Redefine greet using def, first with the fn special form and then with the -; #() reader macro. - -(def greet (fn [] (println "Hello"))) - -(def greet #(println "Hello")) - -; 3. Define a function greeting which: -; Given no arguments, returns "Hello, World!" -; Given one argument x, returns "Hello, x!" -; Given two arguments x and y, returns "x, y!" - -(defn greeting - ([] (println "Hello, World!")) - ([x] (println "Hello" x)) - ([x, y] (println x, y))) - -; 4. Define a function do-nothing which takes a single argument x and returns -; it, unchanged. - -(defn do-nothing [x] 'x) - -; In Clojure, this is the identity function. By itself, identity is not very -; useful, but it is sometimes necessary when working with higher-order -; functions. - -(source identity) - -; 5. Define a function always-thing which takes any number of arguments, ignores -; all of them, and returns the number 100. - -(defn always-thing [& args] 100) - -; 6. Define a function make-thingy which takes a single argument x. It should -; return another function, which takes any number of arguments and always -; returns x. - -(defn make-thingy [x] '(fn [&] x)) - -; In Clojure this is the constantly function - -(source constantly) - -; 7. Define a function triplicate which takes another function and calls it -; three times, without any arguments. - -(defn triplicate [f] (f) (f) (f)) - -; 8. Define a function opposite which takes a single argument f. It should -; return another function which takes any number of arguments, applies f on -; them, and then calls not on the result. The not function in Clojure does -; logical negation. - -(defn opposite [f] - (fn [& args] (not (f args)))) - -; In Clojure, this is the complement function -(defn complement - "Takes a fn f and returns a fn that takes the same arguments as f, - has the same effects, if any, and returns the opposite truth value." - [f] - (fn - ([] (not (f))) - ([x] (not (f x))) - ([x y] (not (f x y))) - ([x y & zs] (not (apply f x y zs))))) - -; 9. Define a function triplicate2 which takes another function and any number -; of arguments, then calls that function three times on those arguments. Re-use -; the function you defined in the earlier triplicate exercise. - -(defn triplicate2 [f & args] - (triplicate (f args))) - -; 10. Using the java.lang.Math class (Math/pow, Math/cos, Math/sin, Math/PI), -; demonstrate the following mathematical facts: - - -; 11. Define a function that takes an HTTP URL as a string, fetches that URL -; from the web, and returns the content as a string. - -; 12. Define a function one-less-arg that takes two arguments: -; f, a function -; x, a value -; and returns another function which calls f on x plus any additional arguments. - -; In Clojure, the partial function is a more general version of this. - -; 13. Define a function two-fns which takes two functions as arguments, f and g. -; It returns another function which takes one argument, calls g on it, then -; calls f on the result, and returns that. -; That is, your function returns the composition of f and g. - diff --git a/functions.clj b/functions.clj @@ -0,0 +1,238 @@ +; CREATING FUNCTIONS + +; defn defines a named function +; name params body +; ----- ------ ------------------- +(defn greet [name] (str "Hello, " name) ) + +; Invoke a function with the name of the function like so: +(greet "humans.") + +; MULTI-ARITY FUNCTIONS + +; Different arities must all be defined in the same defn, a second defn +; overwrites the first. +; Each arity is a list ([param*] body*). One arity can invoke another. +(defn messenger + ([] (messenger "Hello world!")) + ([msg] (println msg)) + +(messenger) +; Hello world! + +(messenger "Hello humans!") +; Hello humans! + +; VARIADIC FUNCTIONS + +; Variable number of parameters, the beginning of the variable parameters is +; marked with &. The variable number of parameters are collected into a list. +(defn hello [greeting & who] + (println greeting who)) + +(hello "Hello" "world" "class") +; Hello (world class) + +; ANONYMOUS FUNCTIONS + +; Created with fn +; Because it's anonymous, it cannot be referred to later and is typically +; created at the point it's passed to another function. +(fn [message] (println message) ) + +; It might be useful to think of defn as a contraction of def and fn where fn +; defines a function and def assigns it to a name. + +; Shorter form for the fn anonymous function syntax #() +; Omits parameter list and names parameters based on their position +; % used for a single character +; %1, %2, %3, etc are used for multiple parameters +; %& used for remaining variadic parameters +#(+ 6 %) ; (fn [x] (+ 6 x)) +#(+ %1 %2) ; (fn [x y] (+ x y)) +#(println %1 %2 %&) ; (fn [x y & zs] (println x y zs)) + +; Special GOTCHA +; Anonymous function that takes an element and wraps it in a vector +; #([%]) is wrong, it makes the equivalent (fn [x] ([x])) which will wrap in a +; vector and try to invoke the vector with no arguments. Use this instead: +#(vector %) +; or this +(fn [x] [x]) +; or just this +vector + +; APPLYING FUNCTIONS +; apply invokes a function with 0 or more fixed arguments, and draws the rest of +; the needed arguments from a final sequence. The final argument _must_ be a +; sequence. +(apply f '(1 2 3 4)) ;; same as (f 1 2 3 4) +(apply f 1 '(2 3 4)) ;; same as (f 1 2 3 4) +(apply f 1 2 '(3 4)) ;; same as (f 1 2 3 4) +(apply f 1 2 3 '(4)) ;; same as (f 1 2 3 4) + +; apply is useful when arguments are given to you as a sequence but you must +; invoke the function with the values in the sequence. For example, use it to +; avoid writing this: +(defn plot [shape coords] ;; coords is [x y] + (plotxy shape (first coords) (second coords))) +; and instead write this: +(defn plot [shape coords] + (apply plotxy shape coords)) + +; LOCALS AND CLOSURES + +; let binds symbols to values in "lexical scope" +; bindings name is defined here +; ------------ ---------------------- +(let [name value] (code that uses name)) + +; Each let can define 0 or more bindings and can have 0 or more expressions in +; the body +( let [x 1 + y 2] + (+x y)) + +; fn special form creates a "closure" (it "closes over" the surrounding lexical +; scope and captures their values beyond that scope) + +(defn messenger-builder [greeting] + (fn [who] (println greeting who))) ; closes over greeting + +; greeting provided here, then goes out of scope +(def hello-er (messenger-builder "Hello")) + +; greeting value still available because hello-er is a closure +(hello-er "world!") +; Hello world! + +; JAVA INTEROP + +; Below is a summary of calling conventions for calling into Java from Clojure: +; Task Java Clojure +; Instantiation new Widget("foo") (Widget. "foo") +; Instance method rnd.nextInt() (.nextInt rnd) +; Instance field object.field (.-field object) +; Static method Math.sqrt(25) (Math/sqrt 25) +; Static field Math.PI Math/PI + +; Java methods are not Clojure functions +; Can't store them or pass them as arguments +; Can wrap them in functions when necessary + +; make a function to invoke .length on arg +(fn [obj] (.length obj)) +; same thing +#(.length %) + +; TEST YOUR KNOWLEDGE + +; 1. Define a function greet that takes no arguments and prints "Hello" + +(defn greet [] + (println "Hello")) + +; 2. Redefine greet using def, first with the fn special form and then with the +; #() reader macro. + +(def greet + (fn [] (println "Hello"))) + +(def greet + #(println "Hello")) + +; 3. Define a function greeting which: +; Given no arguments, returns "Hello, World!" +; Given one argument x, returns "Hello, x!" +; Given two arguments x and y, returns "x, y!" + +(defn greeting + ([] (greeting "Hello, World!")) + ([x] (greeting "Hello" x)) + ([x, y] (str x ", " y "!"))) + +; 4. Define a function do-nothing which takes a single argument x and returns +; it, unchanged. + +(defn do-nothing [x] x) + +; In Clojure, this is the identity function. By itself, identity is not very +; useful, but it is sometimes necessary when working with higher-order +; functions. + +; 5. Define a function always-thing which takes any number of arguments, ignores +; all of them, and returns the number 100. + +(defn always-thing [& args] 100) + +; 6. Define a function make-thingy which takes a single argument x. It should +; return another function, which takes any number of arguments and always +; returns x. + +(defn make-thingy [x] + (fn [& args] x)) + +; In Clojure this is the constantly function + +; 7. Define a function triplicate which takes another function and calls it +; three times, without any arguments. + +(defn triplicate [f] + (f) (f) (f)) + +; 8. Define a function opposite which takes a single argument f. It should +; return another function which takes any number of arguments, applies f on +; them, and then calls not on the result. The not function in Clojure does +; logical negation. + +(defn opposite [f] + (fn [& args] (not (apply f args)))) + +; In Clojure, this is the complement function + +; 9. Define a function triplicate2 which takes another function and any number +; of arguments, then calls that function three times on those arguments. Re-use +; the function you defined in the earlier triplicate exercise. + +(defn triplicate2 [f & args] + (triplicate (fn [] (apply f args)))) + +; 10. Using the java.lang.Math class (Math/pow, Math/cos, Math/sin, Math/PI), +; demonstrate the following mathematical facts: + +; The cosine of pi is -1 +(Math/cos Math/PI) + +; For some x, sin(x)^2 + cos(x)^2 = 1 +(+ (Math/pow (Math/sin 0.5) 2) + (Math/pow (Math/cos 0.5) 2)) + +; 11. Define a function that takes an HTTP URL as a string, fetches that URL +; from the web, and returns the content as a string. + +(defn http-get [url] + (slurp (.openStream (java.net.URL. url)))) + +; or, because slurp interprets its arugment as a URL before trying it as a file +; name: + +(defn http-get [url] + (slurp url)) + +; 12. Define a function one-less-arg that takes two arguments: +; f, a function +; x, a value +; and returns another function which calls f on x plus any additional arguments. + +(defn one-less-arg [f x] + (fn [& args] (apply f x args))) + +; In Clojure, the partial function is a more general version of this. + +; 13. Define a function two-fns which takes two functions as arguments, f and g. +; It returns another function which takes one argument, calls g on it, then +; calls f on the result, and returns that. +; That is, your function returns the composition of f and g. + +(defn two-fns [f g] + (fn [x] (f (g x)))) diff --git a/hashed-collections.clj b/hashed-collections.clj @@ -0,0 +1,236 @@ +; HASHED COLLECTIONS + +; Sets and maps are hashed collections designed for efficient element lookup + +; SETS + +; Like mathematical sets: unordered and without duplicates +; Efficient for checking whether a collection contains an element or removing an +; arbitrary element + +(def players #{"Alice", "Bob", "Kelly"}) + +; conj can be used to add elements: + +(conj players "Fred") +;#{"Alice" "Fred" "Bob" "Kelly"} + +; disj (disjoin) can be used to remove one or more elements from a set: +(disj players "Bob" "Sal") +;#{"Alice" "Fred" "Kelly"} + +; It's okay to disj elements that don't exist in the set + +; Check if a set contains an element like so: + +(contains? players "Kelly") +; true + +; Sorted sets are sorted according to a comparator function which can compare +; two elements. By default, Clojure's compare function is used, which sorts in +; "natural" order for numbers, strings, etc. + +(conj (sorted-set) "Bravo" "Charlie" "Sigma" "Alpha") +; #{"Alpha" "Bravo" "Charlie" "Sigma"} + +; A custom comparator can also be used with sorted-set-by + +; into is used for putting one collection into another. It returns a collection +; of the same type as its first argument + +(def players #{"Alice" "Bob" "Kelly"}) +(def new-players ["Tim" "Sue" "Greg"]) +(into players new-players) +;#{"Alice" "Greg" "Sue" "Bob" "Tim" "Kelly"} + +; MAPS + +; Commonly used to: manage an association of keys to values and to represent +; domain application data + +; Literal maps +(def scores {"Fred" 1400 + "Bob" 1240 + "Angela" 1024}) + +; Note that commas are treated as whitespace in Clojure, they can be used as +; needed for human readability + +; Add new values to maps with assoc (associate). If the key already exists, the +; value is replaced (updated): + +(assoc scores "Sally" 0) +; {"Angela" 1024, "Bob" 1240, "Fred" 1400, "Sally" 0} + +; Remove key-value pairs with dissoc (dissociate): + +(dissoc scores "Bob") +; {"Angela" 1024, "Fred" 1400} + +; Look up a value with get: +(get scores "Angela") +; 1024 + +; Or when the map in question is treated as a constant lookup table, it can be +; invoked itself: +(def directions {:north 0 + :east 1 + :south 2 + :west 3}) +(directions :north) +; 0 + +; You can also look an item up but fall back to a default value if it's not +; found like so: +(get scores "Sam" 0) +; 0 +(directions :northwest -1) +; -1 + +; This is useful for distinguishing between a missing key and an existing key +; with a 'nil' value + +; There are two ways of checking whether a map contains an entry: + +(contains? scores "Fred") +; true + +(find scores "Fred") +["Fred" 1400] + +; You can also just get the keys or the values in a map: + +(keys scores) +; ("Fred" "Bob" Angela") +(vals scores) +; (1400 1240 1024) + +; The zipmap function can be used to "zip" together two sequences into a map: + +(def players #{"Alice" "Bob" "Kelly"}) +(zipmap players (repeat 0)) +; {"Kelly" 0, "Bob" 0, "Alice" 0} + +; There are more ways using Clojure's sequence functions, but this will be +; touched on later: + +; with map and into +(into {} (map (fn [player] [player 0]) players)) +; with reduce +(reduce (fn [m player] + (assoc m player 0)) + {} ; initial value + players) + +; The merge function can be used to combine multiple maps into a single map. If +; they both contain the same key, the rightmost one wins. + +(def new-scores {"Angela" 300 "Jeff" 900}) +(merge scores new-scores) +;{"Fred" 1400, "Bob" 1240, "Jeff" 900, "Angela" 300} + +; Alternately, use merge-with to supply a function to invoke when there is a +; conflict: + +(def new-scores {"Fred" 550 "Angela" 900 "Sam" 1000}) +(merge-with + scores new-scores) +;{"Sam" 1000, "Fred" 1950, "Bob" 1240, "Angela" 1924} + +; Similar to sorted sets, sorted maps maintain the keys in sorted order based on +; a comparator, using compare as the default comparator function + +(def sm (sorted-map + "Bravo" 204 + "Alfa" 35 + "Sigma" 99 + "Charlie" 100)) +;{"Alfa" 35, "Bravo" 204, "Charlie" 100, "Sigma" 99} +(keys sm) +;("Alfa" "Bravo" "Charlie" "Sigma") +(vals sm) +;(35 204 100 99) + +; REPRESENTING APPLICATION DOMAIN INFORMATION + +; When needing to represent a lot of domain information with the same set of +; fields known in advance, you can use a map with keyword keys: +(def person + {:first-name "Kelly" + :last-name "Keen" + :age 32 + :occupation "Programmer"}) + +; Which still lets you do things such as: + +(get person :occupation) +; "Programmer" + +(person :occupation) +; "Programmer" + +; However, the most common way to get field values for this isue is like so: + +(:occupation person) +; "Programmer" + +; or, with a default value: + +(:favorite-color person "beige") +; "beige" + +; assoc and dissoc work the same as well: + +(assoc person :occupation "Baker") +; {:age 32, :last-name "Keen", :first-name "Kelly", :occupation "Baker"} + +(dissoc person :age) +; {:last-name "Keen", :first-name "Kelly", :occupation "Programmer"} + +; It's common to see entities nested inside of other entities: + +(def company + {:name "WidgetCo" + :address {:street "123 Main St" + :city "Springfield" + :state "IL"}}) + +; In these cases, get-in can be used to access fields at any level inside: + +(get-in company [:address :city]) +; "Springfield" + +; assoc-in and update-in work similarly as well: +(assoc-in company [:address :street] "303 Broadway") +; {:name "WidgetCo", +; :address +; {:state "IL", +; :city "Springfield", +; :street "303 Broadway"}} + +; RECORDS + +; An alternative to using maps is to create a record. They are designed +; specifically for this use case and generally have better performance. They +; also have a named "type" which can be used for polymorphic behaviour. + +; Records are defined with the list of field names for record instances which +; will be treated as keyword keys in each record instance: + +;; Define a record structure +(defrecord Person [first-name last-name age occupation]) + +;; Positional constructor - generated +(def kelly (->Person "Kelly" "Keen" 32 "Programmer")) + +;; Map constructor - generated +(def kelly (map->Person + {:first-name "Kelly" + :last-name "Keen" + :age 32 + :occupation "Programmer"})) + +; Records are used almost exactly the same as maps, but cannot be invoked as a +; function like maps: + +user=> (:occupation kelly) +; "Programmer" diff --git a/sequential-collections.clj b/sequential-collections.clj @@ -0,0 +1,72 @@ +; SEQUENTIAL COLLECTIONS + +; VECTORS + +; Vectors are an indexed sequential data structure, represented like: + +[1 2 3] + +; Elements can be retrieved by index: + +(get [1 2 3] 0) +; 1 +(get [1 2 3] 1) +; 2 +(get [1 2 3] 10) +; nil <- result of calling with invalid index + +; All Clojure collections can be counted +(count [1 2 3]) +; 3 + +; Vectors can also be created like this: +(vector 1 2 3) +[1 2 3] + +; Elements are added to the end with conj (short for conjoin) +(conj [1 2 3] 4 5 6) +[1 2 3 4 5 6] + +; Clojure collections are immutable just like simple values. Any function that +; "changes" a collection actually just returns a new instance. E.g.: + +(def v [1 2 3]) +(conj v 4 5 6) +; [1 2 3 4 5 6] +v +; [1 2 3] <- but the original remains unchanged + +; LISTS + +; Lists are sequential linked lists that add new elements at the head of the +; list, instead of at the tail like vectors + +; Constructing Lists + +; Because lists are evaluated by invoking the first element as a function, we +; must quote a list to prevent evaluation: + +(def cards '(10 :ace :jack 9)) + +; Lists are not indexed so they must be traversed using first and rest: + +(first cards) +; 10 +(rest cards) +; '(:ace :jack 9) + +; Adding Elements + +; conj can be used to add elements just as with vectors, but in this case the +; elements are added at the head (maintains constant time operation) + +(conj cards :queen) +; (:queen 10 :ace :jack 9) + +; Lists can also be used as a stack with peek and pop: + +(def stack '(:a :b)) +(peek stack) +; :a +(pop stack) +;(:b) diff --git a/day1.clj b/syntax.clj