From 9cb904f33fe8cc160ea81669a010ce2ad99f0f99 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Thu, 25 Oct 2018 18:06:55 +0530 Subject: [PATCH 01/62] Refactor ex00. Quickly intro all key concepts like - expressions - namespacing - core syntax - code evaluation rules - def, defn, and fn - lexical scope rules - higher order functions: our own, as well as important built-ins (comp, complement, fnil, and partial) - map, filter, reduce - truthy-ness Remove the ceremony and fanfare of the earlier treatment of these topics. These are generally not alien ideas any more, and we have bigger fish to fry. --- src/clojure_by_example/ex00_introduction.clj | 547 +++++++++++++++---- 1 file changed, 427 insertions(+), 120 deletions(-) diff --git a/src/clojure_by_example/ex00_introduction.clj b/src/clojure_by_example/ex00_introduction.clj index 39737d0..a4ef07c 100644 --- a/src/clojure_by_example/ex00_introduction.clj +++ b/src/clojure_by_example/ex00_introduction.clj @@ -9,151 +9,152 @@ ;; EX00: LESSON GOAL: -;; - A very quick intro to Clojure syntax, just to familiarize your -;; eyes with it. -;; -;; - Don't get stuck here! -;; - Run through it once, try evaluating expressions of interest -;; and move on to EX01. -;; - Your eyes and brain will adjust fairly quickly, as you -;; work through the examples to follow. +;; - Drill some Clojure basics, to set up the sections +;; to follow (and generally, to help grok code in the wild) +;; - Familiarize one's eyes with Clojure syntax +;; - Understand Clojure's evaluation model +;; - Introduce a handful of important concepts, +;; data structures, expressions, and functions +;; that are used pervasively. +;; - Start using an interactive development workflow +;; right away -;; Clojure is a "Lisp" -;; - Lisp is short for List Processing -;; - It's just another way to design a programming language -;; (Ignore "But, Why?" for now... Just use it as it is, and try to -;; do something practical with it.) +;; All Clojure code is composed of "expressions": +;; - And, all Clojure expressions evaluate to a value. +;; - "Atomic" literals: +"hello" ; strings +:hello ; keywords +\h \e \l \l \o ; characters +'hello ; symbols +42 ; numbers +22/7 ; fractional numbers +nil ; yes, nil is a value +;; - Collection literals: +[1 2 3 4 5] ; a vector +{:a 1 :b 2} ; a hash-map (key-value pairs) +#{1 2 3 4 5} ; a hash-set +'(1 2 3 4 5) ; a list -;; Clojure code is composed of "expressions": +;; - "Built-in" functions: ++ ; addition +map ; map over a collection +filter ; filter from a collection +reduce ; transform a collection -;; These literal values are Clojure "expressions" -"hello" ; strings -:hello ; keywords -'hello ; symbols -42 ; numbers -22/7 ; fractional numbers +;; - "Symbolic" expressions (or "s"-expression or s-expr) -;; "Built-in" functions are also "expressions" -;; - We will meet all of these again, very soon. -+ ; addition -map ; map over a collection -filter ; filter from a collection -reduce ; transform a collection +(+ 1 2) ; a simple s-expr +(+ (+ 1 2) (+ 1 2)) ; an s-expr of nested s-exprs -;; Collection "literals" are expressions too: -;; - We will extensively use such "collection" data structures. -[1 2 3 4 5] ; a vector -{:a 1 :b 2} ; a hash-map -#{1 2 3 4 5} ; a hash-set -'(1 2 3 4 5) ; a list +(+ (+ (+ 1 2) (+ 1 2)) + (+ (+ 1 2) (+ 1 2))) ; an even more nested s-expr +(defn same + [x] + x) ; function definitions are also s-exprs -;; Clojure code is also composed of expressions; -;; - we refer to them as "symbolic" expressions (or "s"-expression) -(+ 1 2) ; an s-expression +;; Namespaces: +;; +;; - are how we organize and/or modularise code +;; - all Clojure code is defined and evaluated within namespaces -(+ (+ 1 2) (+ 1 2)) ; an s-expression of nested expressions +;; Evaluate and see: -(+ (+ (+ 1 2) (+ 1 2)) - (+ (+ 1 2) (+ 1 2))) ; an even more nested s-expression +map ; is defined in the `clojure.core` ns (namespace) +same ; is defined in the current ns -;; In fact, ALL Clojure code is just "expressions" -;; - And, all Clojure expressions evaluate to a value. -;; -;; - All literals evaluate to themselves. They are values. -;; (Hence "literal": a literal is what it is. :-D) -;; -;; - All collection literals also evaluate to themselves. -;; (A literal collection is what it is, too.) -;; -;; - All functions are values. -;; (More on this a little later) -;; -;; - All s-expressions, however deeply nested, finally evaluate -;; to a return value. Expressions evaluate to either a literal, -;; or a collection, or a function. +#_(ns-name *ns*) ; What's the current ns? ;; Clojure expression syntax rules: -;; + ;; - Literals: ;; - Just write them down -;; -;; - Collection Literals: -;; - Just write them down too, but also -;; - make sure opening brackets are always matched by closing brackets -;; [1 2 3] is a well-formed vector representation -;; [1 2 3 is an "unbalanced" vector and will cause an error. -;; -;; - Symbolic expressions ("s-expressions"): -;; - Make sure the round parentheses close over the intended/required -;; sub-expressions -;; (+ 1 2) is a well-formed expression that will be evaluated -;; (+ 1 2 is an "unbalanced" s-expression and will cause an error. +;; - Collections and S-expressions: +;; - Always. Be. Closing. -;; Clojure Code Evaluation Rules: -;; -;; - To instruct Clojure to evaluate a list of expressions, -;; enclose the expressions in round parentheses. -;; - Recall: (+ 1 2) -;; -;; - The very first expression after an opening paren MUST be -;; a function. -;; - So: (1 2) will fail, because 1 is not a function -;; -;; - All expressions or sub-expressions that follow the first expression -;; will first be fully evaluated into values, and then passed to -;; the first expression as arguments. -;; - Recall: (+ (+ (+ 1 2) (+ 1 2)) -;; (+ (+ 1 2) (+ 1 2))) -;; -;; - You may mentally evaluate the above form "inside-out", like this: -;; - Evaluate the smallest and innermost expressions first, -;; - Mentally replace them with their return values -;; - Pass those values as arguments to the next higher expression -;; - Continue until you are left with a literal value. -;; -;; (+ (+ (+ 1 2) (+ 1 2)) -;; (+ (+ 1 2) (+ 1 2))) -;; -;; (+ (+ 3 3 ) -;; (+ 3 3 )) -;; -;; (+ 6 -;; 6) -;; -;; 12 -;; -;; - Keep this evaluation model in mind, when you read Clojure code, -;; to figure out how the code will evaluate. -;; -;; - To prevent evaluation, explicitly mark an expression as a list -;; '(1 2) put a single quote in front of an expression to tell Clojure -;; you don't want it to evaluate that expression. -;; -;; - To comment out in-line comment text, or even an expression, -;; place one or more semi-colons before the text/expression. +;; [1 2 3] ; OK +;; [1 2 3 ; FAIL + +;; {:a 1 :b 2} ; OK +;; {:a 1 :b 2 ; FAIL + +;; (+ 1 2) ; OK +;; (+ 1 2 ; FAIL + +;; - Indentation, extra spaces, and commas are just for +;; our reading convenience. Example: all of the following +;; literal maps represent the same value: + +{:a 1 :b 2} + +{:a 1, :b 2} + +{:a 1, + :b 2} + +{:a 1 + :b 2} + +{:a 1 + :b + 2} + + + +;; Clojure Expression Evaluation Rules: + +(+ 1 2) + +;; - Wrap in parentheses to cause evaluation + +;; - First position is special, and must be occupied by a function +;; (1 2) ; FAIL, because 1 is not a function + +;; - All s-expressions, however deeply nested, finally evaluate +;; to a return value (a literal, or a collection, or a function.) + +;; - Mentally evaluate nested expressions "inside-out": ;; -;; - `#_` is a clean way to comment out multi-line s-expressions -;; Compare this: -#_(+ 1 2 - 3 4 - 5 6) -;; With this: -;; (+ 1 2 -;; 3 4 -;; 5 6) +(+ (+ (+ 1 2) (+ 1 2)) + (+ (+ 1 2) (+ 1 2))) + +(+ (+ 3 3 ) + (+ 3 3 )) + +(+ 6 + 6) + +12 + +;; - _Prevent_ evaluation of s-expression by "quoting" it, +;; i.e. explicitly marking a list: + +'(+ 1 2) ; but the list will still remain in the evaluation path +;; - Prevent evaluation and _elide_ any s-expression +;; (the special "#_" syntax is called a "reader macro"). -;; Why is Clojure a "List Processing" language? +(+ (+ (+ 1 2) (+ 1 2)) + #_(+ (+ 1 2) (+ 1 2))) ; elide the nested sub-expression from execution path + +;; - Entirely _ignore_ code and free-form text by commenting out +;; with one or more semicolons: +; ( + 1 2 +;; 3 4 +;;; 5 6) + + + +;; Why is Clojure a Lisp ("LISt Processing") language? '(+ 1 2) ; Recall: this is a Clojure list, that Clojure evaluates ; as literal data. @@ -162,8 +163,8 @@ reduce ; transform a collection ; as an executable list, and tries to evaluate it as code. -;; More generally, Clojure code, like other lisps, is written -;; in terms of its own data structures. For example: +;; More generally, Clojure code is written in terms of Clojure's +;; own data structures. For example: ;; ;; Here is a function definition. (defn hie @@ -189,6 +190,312 @@ reduce ; transform a collection +;; Basic Function Syntax +;; +;; - Named functions: +;; +(defn function-name + "Documentation string (optional)." + [arg1 arg2 arg3 etc up to argN] + 'function 'body + 'goes 'here + '...) + +;; - Anonymous functions: +;; +(fn [arg1 arg2 arg3 etc up to argN] + 'function 'body + 'goes 'here + '...) + + + +;; A dead-simple function: + +(defn same + "Simply return the input unchanged." + [x] + x) + + +(fn [x] x) ; just like `same`, but with no name + + + +;; EXERCISE +;; +;; Evaluate and see: + +(same 42) + +(same [1 2 3 4 5]) + +(same {:pname "Earth" :moons 1}) + + +;; How about the anonymous version of `same`? +;; - What's the evaluation model? + +((fn [x] x) 42) + +((fn [x] x) [1 2 3 4 5]) + +((fn [x] x) {:pname "Earth" :moons 1}) + + +;; What should happen here? +(= 'FIX + (same same) + ((fn [x] x) same)) + + +;; `identity` +;; - provided by Clojure +;; - is exactly like our`same` function +;; - is extremely general (accepts any value) +;; - is surprisingly useful, as we will discover later + +;; Fix this to prove `identity` and `same` are the same: +;; - Note: Functions are values and can therefore be compared. +;; +(= identity + (same identity) + ((fn [x] x) identity) + (identity identity)) +;; +;; Now, evaluate this in the REPL to _see_ the truth: +;; (clojure.repl/source identity) + + + +;; And to round it up... functions can accept, as well as +;; return functions. + +(defn gen-identity + [] ; zero arguments + identity) + + +(= identity + (gen-identity)) + + +(defn selfie + "Given a function `f`, return the result of + applying `f` to itself." + [f] + (f f)) + + +(= 42 + (identity 42) + ((selfie identity) 42) + ((selfie (selfie identity)) 42) + ((selfie (selfie (selfie identity))) 42)) ; ad-infinitum + + +;; Compose (chain) functions with `comp` +((comp inc inc inc) 39) + +;; Negate predicates with `complement` +((complement string?) "hi") + +;; Use `fnil` to "nil-patch" functions that cannot sanely handle nil (null) inputs +#_(+ nil 1) ; FAIL +((fnil + 0) nil 1) ; OK + +;; Juxtapose functions with `juxt` (place results "side-by-side") +((juxt inc identity dec) 42) + + + +;; Lexical Scope in Clojure + + +;; EXERCISE: +;; Reason about the following expressions. +;; - Mentally evaluate and predict the results; then check. + +(def x 42) ; Bind `x` to 42, globally ("top-level" binding) + +(identity x) ; obviously returns 42 + +((fn [x] x) x) ; also returns 42, but how? + +(let [x 10] ; we use `let` to bind things locally + (+ x 1)) + + +;; EXERCISE: +;; Read carefully, and compare these three function variants: + +(defn add-one-v1 + [x] + (+ x 1)) ; which `x` will this `x` reference? + +(add-one-v1 1) ; should evaluate to what? +(add-one-v1 x) ; should evaluate to what? + + +(defn add-one-v2 + [z] + (+ x 1)) ; which `x` will this `x` reference? + +(add-one-v2 1) ; should evaluate to what? +(add-one-v2 x) ; should evaluate to what? + + +(defn add-one-v3 + [x] + (let [x 10] + (+ x 1))) ; which `x` will this `x` reference? + +(add-one-v3 1) ; should evaluate to what? +(add-one-v3 x) ; should evaluate to what? + + +;; `let` +;; - Mentally evaluate these, predict the results, +;; and try to infer the scoping rule. +;; - Start with any `x`, and mechanically work +;; your way around. + +((fn [x] x) (let [x 10] x)) + + +((fn [x] x) (let [x x] x)) + + +(let [x 10] ((fn [x] x) x)) + + +((let [x 10] (fn [x] x)) x) + + + +;; Function Closure: +;; - Read carefully and work out what gets bound to `scale-by-PI`. + +(defn scale-by + "Given a number `x`, return a function that accepts + another number `y`, and scales `y` by `x`." + [x] + (fn [y] (* y x))) + +(def PI 3.141592653589793) + +((scale-by PI) 10) + + +;; Achieve the same with `partial` application +(def scale-by-PI (partial * PI)) + +(scale-by-PI 10) + + +;; Strict lexical scope allows us to mechanically work out +;; where a value originated. +;; - Start at the place of reference of the value. +;; - Then "walk" outwards, until you meet the very first let binding, +;; or argument list, or def, where the value was bound. +;; - Now you know where the value came from. + + + +;; Sequences (or Collections), and operations on Sequences +;; - Clojure provides _many_ sequence functions. +;; Here are some important ones: + +map +;; Basic Syntax: +;; +;; (map a-function a-collection) +;; +;; Where the function must accept exactly one argument, because +;; it must transform only one item of the input at a time. + +(map inc [1 2 3 4 5 6]) +;; | | | | | | ; declare a mapping of each item of the input coll +;; inc inc inc ; via `inc` +;; | | | | | | +;; (2 3 4 5 6 7) ; to each item of the output coll + + +filter +;; Basic Syntax: +;; +;; (filter a-predicate-fn a-collection) +;; +;; Where the function must accept exactly one argument and +;; return a truthy result (hence we term it a "predicate" function). + +(filter even? [1 2 3 4 5 6]) + +(filter identity [1 nil 3 nil 5 nil]) ; nil is falsey, non-nils are truthy + + +reduce +;; Basic Syntax: +;; +;; (reduce a-function accumulator a-collection) +;; +;; Where the function must accept two arguments: +;; - first one is the value of the accumulator it manages, and +;; - the second one is bound to each item of the collection + +(reduce + 0 [0 0 1 2]) + +;; Imagine each step of the above computation, like this: + +;; ======================================= +;; Accumulator | Input collection (of number of moons) +;; ======================================= +;; 0 (start) | [0 0 1 2] ; just before first step +;; 0 | [0 1 2] ; at end of first step +;; 0 | [1 2] +;; 1 | [2] +;; 3 | [] ; reduce detects empty collection +;; --------------------------------------- +;; 3 (return value) ; reduce spits out the accumulator + + + +;; Truthiness +;; +;; - Only `nil` and `false` are Falsey; everything else +;; is Truthy +;; - a "predicate" function can return Truthy/Falsey, +;; not just boolean true/false +;; - we can make good use of this behaviour, in Clojure + + +(def a-bunch-of-values + [nil, false, ; falsey + 42, :a, "foo", true, ; truthy + {:a 1, :b 2}, [1 2 3 4], ; truthy + '(), {}, [], ""]) ; truthy + + +;; A quick proof: +(map boolean ; coerces a given value to boolean true or false + a-bunch-of-values) + +(filter boolean + a-bunch-of-values) + + +;; Branching logic accepts Truthy/Falsey + +(if nil ; if condition is Truthy + "hi!" ; then evaluate the first expression + "boo!") ; else evaluate the second expression + + +(when false ; only when the condition is truthy + "boo!") ; evaluate the body. Otherwise, always return `nil` + + + ;; RECAP: ;; ;; - All Clojure code is a bunch of "expressions" From c9aba95a58bb81c171fa929a96fa11d4a65cf6bd Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Thu, 25 Oct 2018 18:10:39 +0530 Subject: [PATCH 02/62] Refactor ex01. Add destructuring, collections, and start operating on collections of planets right away. Clarify intent to model domain as data. Introduce all the important ways to query data structures including destructuring. Set us up to segue into planetary stats calculation, using functions with well-designed APIs --- .../ex01_domain_as_data.clj | 276 +++++++++++ .../ex01_small_beginnings.clj | 464 ------------------ 2 files changed, 276 insertions(+), 464 deletions(-) create mode 100644 src/clojure_by_example/ex01_domain_as_data.clj delete mode 100644 src/clojure_by_example/ex01_small_beginnings.clj diff --git a/src/clojure_by_example/ex01_domain_as_data.clj b/src/clojure_by_example/ex01_domain_as_data.clj new file mode 100644 index 0000000..a07d14f --- /dev/null +++ b/src/clojure_by_example/ex01_domain_as_data.clj @@ -0,0 +1,276 @@ +(ns clojure-by-example.ex01-domain-as-data) + + +;; Ex01: LESSON GOAL: +;; - Model and query things using pure data +;; - See how to "de-structure" data (it's a powerful, flexible lookup mechanism) +;; - Leverage de-structuring to design a self-documenting function API + + +;; Our Earth + +;; "pname" "Earth" +;; "mass" 1 ; if Earth mass is 1, Jupiter's mass is 317.8 x Earth +;; "radius" 1 ; if Earth radius is 1, Jupiter's radius is 11.21 x Earth +;; "moons" 1 +;; "atmosphere" "nitrogen" 78.08 +;; "oxygen" 20.95 +;; "CO2" 0.40 +;; "water-vapour" 0.10 +;; "argon" 0.33 +;; "traces" 0.14 + + +;; Recall: Literal syntax: +;; - If we just put curly braces in the right places, +;; we can turn the given table into a Clojure hash-map: + +(def earth + {"pname" "Earth" + "mass" 1 + "radius" 1 + "moons" 1 + "atmosphere" {"nitrogen" 78.08 + "oxygen" 20.95 + "carbon-dioxide" 0.4 + "water-vapour" 0.10 + "argon" 0.33 + "traces" 0.14}}) + +;; Now we can look up any value using `get`, and `get-in`: + +;; with `get` +(get earth "pname") + +(get (get earth "atmosphere") + "traces") + + +;; more conveniently, with `get-in` +(get-in earth ["pname"]) + +(get-in earth ["atmosphere" "traces"]) +;; '--> imagine this as a "path" to the value + + + +;; Alternatively, we can model the earth this way, +;; using keywords as keys, to great benefit: +(def earth-alt + {:pname "Earth" + :mass 1 + :radius 1 + :moons 1 + :atmosphere {:nitrogen 78.08 + :oxygen 20.95 + :carbon-dioxide 0.4 + :water-vapour 0.10 + :argon 0.33 + :traces 0.14}}) + +;; `get` and `get-in` work as expected + +(get (get earth-alt :atmosphere) + :traces) + + +(get-in earth-alt [:atmosphere :traces]) + + +;; BUT, unlike plain old strings, keywords also behave as _functions_ +;; of hash-maps, and can look themselves up in hash-maps. + +;; ("pname" earth) ; Will FAIL! + +(:pname earth-alt) ; Works! + +(:argon (:atmosphere earth-alt)) + + +;; Which means we can use keywords in this manner: + +(def planets + [{:pname "Mercury" :moons 0 :mass 0.0533} + {:pname "Venus" :moons 0 :mass 0.815} + {:pname "Earth" :moons 1 :mass 1} + {:pname "Mars" :moons 2 :mass 0.107}]) + + +;; Instead of having to write functions to query planets: +(map (fn [p] (get p :pname)) + planets) + +;; We can directly use keywords as functions: +(map :pname + planets) + + +;; Find planets with less mass than the Earth + +(defn less-mass-than-earth? + [planet] + (< (:mass planet) 1)) + +(filter less-mass-than-earth? + planets) + + +;; Compute total mass of planets having +;; less mass than the Earth: +(reduce + 0 + (map :mass + (filter less-mass-than-earth? + planets))) + + +;; "De-structuring" + +;; - Clojure lets us reach into data structures in arbitrary ways, +;; and extract multiple values in one go +;; +;; - We use this for clean lookups in `let` bindings, and +;; in function signatures, to design expressive APIs. + + +;; Positional De-structuring +;; +;; - Pull apart sequential, ordered data structures like +;; lists, vectors, and any other sequence with linear access +;; +;; - Follow the structure of the collection, and mechanically +;; bind values to symbols by position. + + +;; Instead of looking up values by index position: +(str "The first two planets: " + (:pname (get planets 0)) " and " + (:pname (get planets 1)) ".") + + +;; We can bind symbols by position (match structure to structure): +(let [[m, v] planets] + (str "The first two planets: " + (:pname m) " and " + (:pname v) ".")) + + +;; Use underscores `_` to mark values we don't care for. +(let [[_, _, e] planets] + (str (:pname e) + " is the third rock from the Sun.")) + + +;; Use `:as` to also alias the whole structure. +(let [[_, _, e :as planet-names] (map :pname planets)] + {:useless-trivia (str e " is the third rock from the Sun.") + :planet-names planet-names}) + + +;; "Associative" De-structuring +;; +;; - Syntax to reach into associative data structures +;; (having key-value semantics), in arbitrary ways. +;; +;; - Note: Clojure "Records" and vectors are associative too +;; +;; - Follow the structure of the collection, and mechanically +;; bind values to symbols by key name. + + +;; Instead of looking up values like this: +(:pname earth-alt) + +;; We can follow the map's structure like this: +(let [{p :pname} earth-alt] + p) + +;; Compose it with positional destructuring: +(let [[_, _, {e :pname}] planets] + (str e " is the third rock from the Sun.")) + + +;; And instead of doing lookups one at a time: +(str (:pname earth-alt) " has " + (:moons earth-alt) " moon, " + (:oxygen (:atmosphere earth-alt)) "% Oxygen, " + (:argon (:atmosphere earth-alt)) "% Argon, and " + (:traces (:atmosphere earth-alt)) "% trace gases.") + + +;; We can arbitrarily de-structure maps, directly: +(let [{p :pname + m :moons + {traces :traces + Ar :argon + O2 :oxygen} :atmosphere} earth-alt] + (str p " has " + m " moon, " + O2 "% Oxygen, " + Ar "% Argon, and " + traces "% trace gases.")) + + +;; The `:keys` form lets us de-structure more concisely: +;; - Note: in this style, we must exactly match spellings of +;; symbol names, with spellings of the keys we wish to bind. +(let [{:keys [pname moons] + {:keys [oxygen argon traces]} :atmosphere} + earth-alt] + (str pname " has " + moons " moon, " + oxygen "% Oxygen, " + argon "% Argon, and " + traces "% trace gases.")) + + +;; More powerfully, the `:keys` form lets us: +;; - extract multiple values, +;; - define fallbacks for missing values, _and_ +;; - alias the original input. +;; All in one shot: + +(defn summarise-planet + [{:keys [pname moons] + {:keys [oxygen argon traces] + :or {oxygen "unknown" + argon "unknown" + traces "unknown"}} :atmosphere + :as planet}] + {:summary (str pname " has " + moons " moon(s), " + oxygen " % O2, " + argon " % Argon, and " + traces" % of trace gases.") + :planet planet}) + +(summarise-planet earth-alt) + +(summarise-planet {:pname "Mercury", :moons 0, :mass 0.0533}) + +(map summarise-planet + planets) + + +;; Self-documenting function API: +;; +;; Last, but not least, de-structuring function arguments +;; automatically gives us self-documenting function signatures. + +#_(clojure.repl/doc summarise-planet) ; Evaluate this in the REPL + +#_(meta #'summarise-planet) + +;; RECAP: +;; +;; - hash-maps let us conveniently represent objects we wish to +;; model and query +;; - We can query hash-maps variously with keywords, `get`, and `get-in` +;; - If we use keywords as keys in hash-maps, querying is dead-simple +;; - We can define our own functions with `defn`, using this syntax: +;; +;; (defn function-name +;; [arg1 arg2 arg3 ... argN] +;; (body of the function)) +;; +;; - Using general-purpose data structures, and writing general-purpose +;; functions lets us do more with less diff --git a/src/clojure_by_example/ex01_small_beginnings.clj b/src/clojure_by_example/ex01_small_beginnings.clj deleted file mode 100644 index 102dd51..0000000 --- a/src/clojure_by_example/ex01_small_beginnings.clj +++ /dev/null @@ -1,464 +0,0 @@ -(ns clojure-by-example.ex01-small-beginnings) - - -;; Ex01: LESSON GOAL: -;; -;; - Show a way to model things with pure data, in the form of hash-maps -;; - Show how to query hash-maps -;; - Introduce the idea of a function -;; - Use the above to show how we can "do more with less" - - -;; Our Earth - -;; "pname" "Earth" -;; "mass" 1 ; if Earth mass is 1, Jupiter's mass is 317.8 x Earth -;; "radius" 1 ; if Earth radius is 1, Jupiter's radius is 11.21 x Earth -;; "moons" 1 -;; "atmosphere" "nitrogen" 78.08 -;; "oxygen" 20.95 -;; "CO2" 0.40 -;; "water-vapour" 0.10 -;; "other-gases" "argon" 0.33 -;; "traces" 0.14 - - -;; Looks like a collection of name-value pairs. To some, it will -;; look like JSON. -;; -;; This intuition is correct. We can describe the Earth by its -;; properties, written as name-value pairs or "key"-value pairs. - -;; If we put curly braces in the right places, it becomes a -;; Clojure "hash-map": - -{"pname" "Earth" - "mass" 1 - "radius" 1 - "moons" 1 - "atmosphere" {"nitrogen" 78.08 - "oxygen" 20.95 - "CO2" 0.40 - "water-vapour" 0.10 - "other-gases" {"argon" 0.33 - "traces" 0.14}}} - - -;; Let's query the Earth. - -;; But first, let's create a global reference to our hash-map, -;; for convenience. - -;; Let's call it `earth`. - - -(def earth - {"pname" "Earth" - "mass" 1 - "radius" 1 - "moons" 1 - "atmosphere" {"nitrogen" 78.08 - "oxygen" 20.95 - "CO2" 0.40 - "water-vapour" 0.10 - "other-gases" {"argon" 0.33 - "traces" 0.14}}}) ; <- evaluate this -;; To evaluate the above `def`: -;; - Place the cursor just after the closing paren `)`, and -;; - evaluate it using your editor's evaluate feature -;; (in LightTable, hit ctrl+Enter on Win/Linux, and cmd+Enter on Mac) - -;; Evaluation will attach (or 'bind') the hash-map to the symbol -;; we have called `earth`. - -earth ; evaluate and check the hash-map - - - -;; _Now_ let's query the 'earth' global... - - -;; Top-level access: - -;; Wait! -;; -;; What do you _expect_ 'get' to do, in the expression below? -;; -;; Try to predict, before you evaluate. - -(get earth "pname") ; <- place cursor after closing paren and evaluate. - - - -;; EXERCISE: -;; -;; How to get number of moons? -;; - Uncomment, and fix the expression below: - -;; (get earth 'FIX) - - -;; EXERCISE: -;; -;; What does the atmosphere contain? -;; - Type your expression below: -;; -;; ('FIX 'FIX 'FIX) - - -;; Lesson: -;; - Given a hash-map and a "key", `get` returns the value -;; associated with the key. -;; - A value can be a string, or a number, or even another hash-map. - - - -;; Nested access: - -;; EXERCISE: -;; -;; Now, how to find what the other gases are, in the atmosphere? -;; - Hint: Mentally replace FIX with the value of "atmosphere". -;; - Now ask yourself, what expression will return that value? - -;; (get 'FIX "other-gases") - - -;; EXERCISE: -;; -;; Now, try to go even deeper, to find how much argon we have? -;; - Hint: now you have to replace 'FIX with a nested expression -;; -;; (get 'FIX "argon") - - - -;; Lesson: -;; - You can put s-expressions inside s-expressions and evaluate the -;; whole thing as one s-expression. - -;; Notes: -;; - You may choose to indent a deeply nested s-expression, for clarity. -;; - For now, indent or don't indent, as per your comfort level. -;; - Later, learn about generally-accepted Clojure code style. -;; (https://github.com/bbatsov/clojure-style-guide) - - -;; A Simple "Function" - -;; Let's make our own function to access any "third" level value... - -(defn get-level-3 ; function name - [planet level1-key level2-key level3-key] ; arguments list - ;; function "body": - (get (get (get planet level1-key) - level2-key) - level3-key)) ; What does the function's "body" look like? - - - -;; Now we can... -(get-level-3 earth "atmosphere" "other-gases" "argon") -(get-level-3 earth "atmosphere" "other-gases" "traces") - - - -;; Keywords as Keys of Hash-maps - -;; Hash-maps so widely-used, and can so conveniently represent things, -;; that Clojure provides a far more convenient way to define hash-maps -;; and query them. - -;; Instead of plain old strings as keys, we can use -;; Clojure "keywords" as keys. - -"moons" ; a string - -:moons ; a keyword - - -;; Like strings and numbers, keywords directly "represent" themselves. -;; (A keyword is a fundamental data type.) -;; _Unlike_ strings, keywords are designed to do special things. - -;; To find out, we must first define an alternative hash-map, -;; - that represents the same data about the Earth, -;; - but with keywords as keys, instead of strings -;; - to let us super-easily query the hash-map, using just keywords -(def earth-alt {:pname "Earth" - :mass 1 - :radius 1 - :moons 1 - :atmosphere {:nitrogen 78.08 - :oxygen 20.95 - :CO2 0.4 - :water-vapour 0.10 - :other-gases {:argon 0.33 - :traces 0.14}}}) - -;; Easier top-level access - -;; EXERCISE: -;; -;; What will these return? - -(:pname earth-alt) - -(:mass earth-alt) - - -;; EXERCISE: -;; -;; How to find the atmosphere? Uncomment,fix, and evaluate: - -;; ('FIX earth-alt) - - -;; EXERCISE: -;; -;; What are the other gases, in the atmosphere? -;; - Hint: Remember, we can nest s-expressions inside s-expressions. -;; - Replace each 'FIX with the appropriate value or s-expression. -;; -;; ('FIX 'FIX) - - -;; EXERCISE: -;; -;; How much argon is present in the atmosphere? -;; Hint: once again, 'FIX with value(s) or nested s-expression(s). - -;; ('FIX 'FIX) - - -;; Clojure provides `get-in`, because nested access is so common. -;; - `get-in` is the cousin of `get` (and the granddaddy of our -;; get-level-3 function!) - -;; Try evaluating each one of these... -(get-in earth-alt [:atmosphere]) -(get-in earth-alt [:atmosphere :other-gases]) -(get-in earth-alt [:atmosphere :other-gases :argon]) -;; '--> imagine this as a "path" to the value - - -;; By the way, the function we defined earlier, is general enough -;; to work with keywords too! -(get-level-3 earth-alt :atmosphere :other-gases :argon) - - -;; EXERCISE: -;; -;; We saw `get-in` work for keywords. Does it work for strings too? -;; Uncomment, fix, and evaluate: - -;; (get-in earth 'FIX) - - -;; Did that work? Why or why not? - - -;; EXERCISE: -;; -;; Use get-in to query other gases from the `earth` hash-map. -;; Type your expression below and evaluate it: - - - -;; EXERCISE: -;; -;; Use get-in to query argon from `earth`'s atmosphere -;; Type your expression below and evaluate it: - - - -;; Lesson: -;; - Given a hash-map and a path "key", `get` returns the value -;; associated with the key. - - - -;; Clojure "Vectors": - -;; The square bracketed things we used with `get-in` are in fact a -;; Clojure datastructure. (Other languages may call these "Arrays".) -["atmosphere"] ; is a vector of one string -[:atmosphere :other-gases] ; is a vector of two keywords - - -;; These are `indexed` collections, i.e. we can query a value in -;; a Vector, if we know what "index" position it occupies in the vector. - - -;; EXERCISE: -;; -;; What will this return? - -(get [:foo :bar :baz] 0) -;; Note: We count position starting at `0`, not `1`, in Clojure - - -;; EXERCISE: -;; -;; What will this return? - -(get-in [:foo [:bar :baz]] [1 1]) - - -;; But we are actually just trying to find the "nth" item, -;; and Clojure gives us... -(nth [:foo :bar :baz] 0) - -(nth (nth [:foo [:bar :baz]] 1) - 1) - -;; Lesson: -;; - `get` and `get-in` are general enough to query Vectors too, -;; using index number. -;; - but we'd rather just look up the `nth` item in Vectors - - -;; Basic Data Modeling: - -;; We rarely use vectors to model objects like the Earth. -;; In Clojure, a hash-map is almost always the best way to model -;; an object that we need to query. -;; -;; But why? - -;; What if we model the earth as a vector, instead of a hash-map? -(def earth-as-a-vector - "Docstring: This vector represents Earth this way: - [Name, Radius, Mass, Moons]" - ["Earth" 1 1 1]) - - -;; Now, how do we query Earth? - - -(defn get-earth-name [] ; empty vector means zero arguments - (nth earth-as-a-vector 0)) - -(get-earth-name) ; call with zero arguments - -(defn get-earth-radius [] - (nth earth-as-a-vector 1)) - -(get-earth-radius) - -(defn get-earth-moons [] - (nth earth-as-a-vector 2)) ; Uh, was it 2 or 3? - -(get-earth-moons) ; did this return Mass, or Moons? - -(:moons earth-alt) ; compare: how obviously we can query :moons in earth-alt - - -;; Further, our custom "getter" functions for Earth's properties, -;; are practically useless for other planets we may wish to also define -;; as vectors. -;; -;; Why? -;; -;; Property positions for other planets may differ from earth. -;; And in vector look-up, position matters. -;; -;; Said another way: "Positional semantics do not scale" -;; -;; Consider the function below: -;; -(defn get-planet-prop - "A function with a dangerous, brittle assumption about - planetary properties." - [planet-as-vector prop-position] - (nth planet-as-vector - prop-position)) - - -;; Lesson: Doing More With Less: - -;; Clojure programmers rely on the power, and general-purpose -;; flexibility of hash-maps, as well as general-purpose functions, -;; to avoid getting stuck in such situations. - -;; While nobody stops us from doing so, using vectors to model an object -;; (like the Earth) is clearly awkward. -;; - We must maintain label/name information about values separately -;; (perhaps in the docstring) -;; - Our custom `get-xyz` functions are also far too "specialized", -;; i.e. we can only sensibly use them to query _only_ the earth. -;; - And it opens us up to a whole host of errors: -;; - we can easily lose track of what value represents what property -;; - what if someone decides to add the number of man-made satellites -;; between mass and moon? - - -;; Lesson-end Exercises: - -;; EXERCISE: -;; -;; Define another planet `mercury`, using keywords as keys. -;; - Ensure all keys are keywords -;; - Ensure braces {} are in the right places, to nest data correctly. -;; -;; Use the information below. -;; -#_(;; FIXME - pname "Mercury" ; has... - moons 0 - mass 0.0553 ; recall we assume Earth mass is 1 - radius 0.383 ; recall we assume Earth radius is 1 - atmosphere ; % of total volume - oxygen 42.0 - sodium 29.0 - hydrogen 22.0 - helium 6.0 - potassium 0.5 - other-gases 0.5) - - - -;; EXERCISE: -;; -;; Query the planet `mercury` in 3 ways: -;; - with nested `get` -;; - with get-in -;; - with keywords -;; Type your solutions below: - - - -;; EXERCISE: -;; -;; Write a custom function to do a two-level deep query on `mercury`. -;; - It should be able to query earth, and earth-alt as well. -;; - name it `get-level-2` -;; -;; Fix the function below: - -#_(defn get-level-2 - ['FIX ...] - 'FIX) - -;; Uncomment and evaluate to check you get the correct values -#_(get-level-2 earth "atmosphere" "oxygen") - -#_(get-level-2 earth-alt :atmosphere :oxygen) - -#_(get-level-2 mercury :atmosphere :oxygen) - - -;; RECAP: -;; -;; - hash-maps let us conveniently represent objects we wish to -;; model and query -;; - We can query hash-maps variously with keywords, `get`, and `get-in` -;; - If we use keywords as keys in hash-maps, querying is dead-simple -;; - We can define our own functions with `defn`, using this syntax: -;; -;; (defn function-name -;; [arg1 arg2 arg3 ... argN] -;; (body of the function)) -;; -;; - Using general-purpose data structures, and writing general-purpose -;; functions lets us do more with less From 6b1d8f187d3f4f0ea7e402a7713e61ea4dcfb9d6 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Thu, 25 Oct 2018 18:14:09 +0530 Subject: [PATCH 03/62] WIP: Tighten ex02 --- src/clojure_by_example/data/planets.clj | 66 +++ .../ex02_small_functions.clj | 437 ++++++++---------- 2 files changed, 253 insertions(+), 250 deletions(-) create mode 100644 src/clojure_by_example/data/planets.clj diff --git a/src/clojure_by_example/data/planets.clj b/src/clojure_by_example/data/planets.clj new file mode 100644 index 0000000..eac8756 --- /dev/null +++ b/src/clojure_by_example/data/planets.clj @@ -0,0 +1,66 @@ +(ns clojure-by-example.data.planets) + +(def target-planets + [{:pname "Mercury" + :mass 0.055 + :radius 0.383 + :moons 0 + :gravity 0.378 + :surface-pressure 0 + :surface-temp-deg-c {:low -170 :high 449} + :rocky? true + :atmosphere {}} ; empty hash map means no atmosphere + + {:pname "Venus" + :mass 0.815 + :radius 0.949 + :moons 0 + :gravity 0.907 + :surface-pressure 92 + :surface-temp-deg-c {:low 465 :high 465} + :rocky? true + :atmosphere {:carbon-dioxide 96.45 :nitrogen 3.45 + :sulphur-dioxide 0.015 :traces 0.095}} + + {:pname "Earth" + :mass 1 + :radius 1 + :moons 1 + :gravity 1 + :surface-pressure 1 + :surface-temp-deg-c {:low -89 :high 58} + :rocky? true + :atmosphere {:nitrogen 78.08 :oxygen 20.95 :carbon-dioxide 0.4 + :water-vapour 0.10 :argon 0.33 :traces 0.14}} + + {:pname "Mars" + :mass 0.107 + :radius 0.532 + :moons 2 + :gravity 0.377 + :surface-pressure 0.01 + :surface-temp-deg-c {:low -125 :high 20} + :rocky? true + :atmosphere {:carbon-dioxide 95.97 :argon 1.93 :nitrogen 1.89 + :oxygen 0.146 :carbon-monoxide 0.056 :traces 0.008}} + + {:pname "Chlorine Planet" + :mass 2.5 + :radius 1.3 + :moons 4 + :gravity 1.5 + :surface-pressure 1 + :surface-temp-deg-c {:low -42 :high 24} + :rocky? true + :atmosphere {:chlorine 100.0}} + + {:pname "Insane Planet" + :mass 42 + :radius 4.2 + :moons 42 + :gravity 10 + :surface-pressure 420 + :surface-temp-deg-c {:low 750 :high 750} + :rocky? false + :atmosphere {:sulphur-dioxide 80.0 :carbon-monoxide 10.0 + :chlorine 5.0 :nitrogen 5.0}}]) diff --git a/src/clojure_by_example/ex02_small_functions.clj b/src/clojure_by_example/ex02_small_functions.clj index 632452b..242ec5a 100644 --- a/src/clojure_by_example/ex02_small_functions.clj +++ b/src/clojure_by_example/ex02_small_functions.clj @@ -1,341 +1,278 @@ -(ns clojure-by-example.ex02-small-functions) - -;; Ex02: LESSON GOALS -;; -;; - Learn to define simple functions, and use them -;; - Learn a little bit about how functions behave -;; - Learn about a few useful built-in Clojure functions -;; (We will use these in later exercises.) -;; - Stitch up ideas in this section with a small insight -;; into how we can use functions on vectors and hash-maps. -;; (Set up your intuition, for later exercises.) - - -;; First, some simple functions: - - -;; We define functions like this: -;; -;; (defn function-name -;; "Documentation string (optional)." -;; [arg1 arg2 arg3 ... argN] -;; (body of the function)) - - -;; `defn` stands for: -;; - "DEfine a FuNction, -;; - _and_ give it a globally-referenced name" - - -(defn same - "Simply return the input unchanged." - [x] - x) - -;; EXERCISE -;; -;; What will these return? - -(same 42) - -(same {:pname "Earth" :moons 1}) - -(same [1 2 3 4 5]) - - -;; We can define name-less functions too, like this: -;; -;; (fn [arg1 arg2 arg3 ... argN] -;; (body of the function)) -;; -;; `fn` is short for "define a FuNction, but _do not_ name it at all" -;; -;; E.g. This behaves _exactly_ like `same`, but it does not have a name: - -(fn [x] x) +(ns clojure-by-example.ex02-small-functions ; current namespace (ns) + ;; "require" and alias another ns as `p`: + (:require [clojure-by-example.data.planets :as p])) - -;; EXERCISE: -;; -;; What will these return? -;; - Hint: _mentally replace_ the function definition `(fn [x] x)` -;; with _any name you like_, and imagine that is the function name. -;; BUT make sure it behaves just like `(fn [x] x)`. - -((fn [x] x) 42) - -((fn [x] x) {:pname "Earth" :moons 1}) - -((fn [x] x) [1 2 3 4 5]) - - -;; We will make good use of such "anonymous" functions soon. -;; Just understand they are just like 'regular' functions, except -;; they do not have a globally-referenced name (hence "anonymous"). - - -;; Reminder: Do More With Less! -;; - Observe that `same` as well as `(fn [x] x)` appear to be capable -;; of accepting _any_ kind of value and returning it. -;; - This function definition is very general-purpose indeed. -;; - We will frequently use this "general-purpose" idea. +;; Ex02: LESSON GOALS +;; - Use stuff we've seen so far to build purely functional logic +;; to process a bunch of planets -;; Some "built-in" functions +;; Here are some target planets: +clojure-by-example.data.planets/target-planets -;; Clojure has a function called `fn?`, that returns true if we -;; pass it something that is a function, and false otherwise. +;; Which we can access more conveniently as: +p/target-planets -;; None of these are functions. So, return `false`. +(map :pname p/target-planets) -(fn? 42) +;; Now, let's colonize planets! -(fn? "moons") -(fn? [1 2 3 4 5]) +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Some constants and utilities +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; We defined the function `same`, and truly, it is a function... -(fn? same) +(def tolerances + "Define low/high bounds of planetary characteristics we care about." + {:co2 {:low 0.1, :high 5.0} + :gravity {:low 0.1, :high 2.0} + :surface-temp-deg-c {:low -125, :high 60}}) -;; Our "anonymous" function, is a function too. But of course! -(fn? (fn [x] x)) -;; And `fn?` itself is a... ______ ? -(fn? fn?) +(defn lower-bound + [tolerance-key] + (get-in tolerances [tolerance-key :low])) -;; Wait a minute...!!! -;; -;; We just passed functions as arguments to `fn?`, and it worked! -;; -;; Is `fn?` special, or can we pass any function to any function? +(defn upper-bound + [tolerance-key] + (get-in tolerances [tolerance-key :high])) -;; Well we know `fn?` is a function... -;; - Suppose we pass `fn?` to `same`, do we get back `fn?` unchanged? -(= fn? - (same fn?)) +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions to test a planet for particular conditions +;; - We make them return truthy/falsey values +;; - We call such functions "predicates" +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn atmosphere-present? + [{:keys [atmosphere] :as planet}] + (not-empty atmosphere)) -;; How about the anonymous version of `same`? -;; Does it return `fn?` unchanged as well? -(= 'FIX - ((fn [x] x) fn?)) +#_(map :pname + (filter atmosphere-present? p/target-planets)) -;; How about this? -;; What do we get if we pass `same` to itself? -(= 'FIX - (same same)) +(defn co2-tolerable? + [{{:keys [carbon-dioxide] + :or {carbon-dioxide 0.0}} :atmosphere + :as planet}] + (<= (lower-bound :co2) + carbon-dioxide + (upper-bound :co2))) -;; Lesson: Functions also behave like "values": -;; -;; - `42` is a value. `"moons"` is a value. `:moons` is a value. -;; - Values represent themselves directly -;; - Values are unique in the whole universe (42 is NOT "forty two") -;; - Values never change. They are constant forever and ever. -;; -;; - All Clojure function also are "values", including user-defined -;; functions (like `same`). And, just like the other values... -;; - We can pass functions as arguments to other functions, -;; without any special syntax or declarations. -;; - Functions can _return_ functions as results. -;; - We can compare any two functions and tell if they are -;; exactly the same thing. +#_(map :pname + (filter co2-tolerable? p/target-planets)) -;; Some other "built-in" Clojure functions +(defn gravity-tolerable? + [{:keys [gravity] :as planet}] + (when gravity + (<= (lower-bound :gravity) + gravity + (upper-bound :gravity)))) -;; Return `true` if even, `false` otherwise -(even? 2) +#_(map :pname + (filter gravity-tolerable? p/target-planets)) -;; Return `true` if odd, `false` otherwise -(odd? 3) -;; Increment by one -(inc 42) +(defn surface-temp-tolerable? + [{{:keys [low high]} :surface-temp-deg-c + :as planet}] + (when (and low high) + (<= (lower-bound :surface-temp-deg-c) + low + high + (upper-bound :surface-temp-deg-c)))) -;; Clojure's `identity` function is exactly like our `same` function. -;; - To prove it, fix the s-expression below so it evaluates to `true`: -(= 'FIX - (identity :moon) - (same :moon)) -;; We will use `identity` in surprisingly useful ways later. +#_(map :pname + (filter surface-temp-tolerable? p/target-planets)) +(def poison-gas? + "A set of poison gases." + #{:chlorine, :sulphur-dioxide, :carbon-monoxide}) -;; Now, let's use simple functions to process Collections -;; `map` -;; -;; `map` a function over a collection... -(map inc [1 2 3 4 5 6]) -;; Such that... -#_( 1 2 3 4 5 6) ; each item of input -;; | | | | | | ; is incremented by `inc` to give a result where -#_( 2 3 4 5 6 7) ; each output item "maps" back to an input item +;; We can use sets as predicates. Sets behave as functions +;; that test for set membership. -;; Note: -;; - Ignore the following subtlety for now: -;; You may have noticed that we passed a vector [] to `map` above, -;; but the result looks like a list (). Well it really isn't a -;; concrete list, but a "sequence" representation of the vector. -;; Clojure wraps a "sequence" in parens (), for display purposes only. -;; -;; - Think of `map` in general terms, as a way to express -;; one-to-one "mappings" of an input sequence to an output sequence, -;; by way of a function. -;; -;; - The syntax of `map` is: -;; -;; (map your-function input-collection) -;; -;; Where 'your-function' must accept exactly one argument, because -;; it must transform only one item of the input at a time. +(poison-gas? :oxygen) ; falsey +(poison-gas? :chlorine) ; truthy -;; EXERCISE: -;; -;; What should the following `map` expression return? -;; - First predict the answer, then evaluate to confirm. -;; - Hint: mentally apply the `even?` function to each item, one by one, -;; and build up a collection of results of each function application. -;; - And ignore the [] v/s () subtlety of input v/s output display. -;; Just predict the sequence of output items, in the correct order. -(map even? [1 2 3 4 5 6]) +(defn air-too-poisonus? + [{:keys [atmosphere] :as planet}] + (some (fn [[gas-name gas-pct]] + (and (poison-gas? gas-name) + (<= 1.0 gas-pct))) + atmosphere)) +(map :pname + (filter air-too-poisonus? p/target-planets)) -;; How about this? +;; a hash-map is a collection of key-value pairs +(map identity + {:nitrogen 78.08, :oxygen 20.95, :carbon-dioxide 0.4, + :water-vapour 0.1, :argon 0.33, :traces 0.14}) -(map odd? [1 2 3 4 5 6]) +(map (fn [[k v]] (str v "% " k)) + {:nitrogen 78.08, :oxygen 20.95, :carbon-dioxide 0.4, + :water-vapour 0.1, :argon 0.33, :traces 0.14}) -;; And this? -(map identity [1 2 3 4 5 6]) ; Recall: `identity` is just like `same` +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Composite checks to +;; - test whether a given planet meets a variety of conditions. +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; And this? +(defn planetary-conditions-meet-constraint? + "Return the given planet as-is, when it meets the condition fns, + as per the constraint fn (like every?, some, not-every). -(map (fn [x] x) [1 2 3 4 5 6]) + The constraint fn must return a Boolean true/false value." + [constraint] + (fn [conditions planet] + (when (true? (constraint (fn [f] (f planet)) + conditions)) + planet))) -;; Nice! Our anonymous "identity" function is a drop-in replacement -;; for `identity`, as well as `same`. +(def planet-meets-any-one-condition? + (planetary-conditions-meet-constraint? + some)) -;; Now for a little bit of fun with `map` and some planets. +(def planet-meets-all-conditions? + (planetary-conditions-meet-constraint? + every?)) -;; This is a Clojure vector... of numbers +(def planet-meets-no-condition? + (planetary-conditions-meet-constraint? + not-any?)) -[1 2 3 4 5 6] +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Conditions for colonisation +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; This is a Clojure vector... of Clojure hash-maps. -[{:pname "Mercury" :moons 0} - {:pname "Venus" :moons 0} - {:pname "Earth" :moons 1} - {:pname "Mars" :moons 2}] ; (Yes we can do this. More on this later!) +(def minimal-good-conditions + "A collection of functions that tell us about the + good-ness of planetary conditions." + [co2-tolerable? + gravity-tolerable? + surface-temp-tolerable?]) -;; Let's name our collection of planets as, um... `planets` +(def fatal-conditions + "A collection of functions that tell us about the + fatality of planetary conditions." + [(complement atmosphere-present?) + air-too-poisonus?]) -(def planets [{:pname "Mercury" :moons 0} - {:pname "Venus" :moons 0} - {:pname "Earth" :moons 1} - {:pname "Mars" :moons 2}]) +(defn habitable? + "We deem a planet habitable, if it has all minimally good conditions, + and no fatal conditions." + [planet] + (planet-meets-no-condition? + fatal-conditions + (planet-meets-all-conditions? + minimal-good-conditions + planet))) -;; Recall that we can query a map, like this: -(:pname {:pname "Mercury" :moons 0}) +#_(map :pname + (filter habitable? p/target-planets)) -;; That is, a keyword _behaves like a function_ of a hash-map. +(defn colonisable? + "We deem a planet colonisable, if it has at least one + minimally good condition, and no fatal conditions." + [planet] + (and (planet-meets-any-one-condition? + minimal-good-conditions + planet) + (planet-meets-no-condition? + fatal-conditions + planet))) -;; EXERCISE -;; -;; What if we pass a keyword instead of a real function, to `map`? -;; - What should the following map expression return? -;; - Predict the answer, and then evaluate to confirm. +#_(map :pname + (filter colonisable? p/target-planets)) -(map :pname planets) -;; Read as: -;; "map :pname over `planets`, which is vector of planet hash-maps" +(defn observe-only? + "We select a planet for orbital observation, if it only has harsh surface conditions." + [planet] + (and (planet-meets-any-one-condition? + fatal-conditions + planet) + (planet-meets-no-condition? + minimal-good-conditions + planet))) +#_(map :pname + (filter observe-only? p/target-planets)) -;; Stitching it all together... +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Enrich planetary data with analytical results +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Let's define another little function. -(defn planet-has-moons? - "Given a 'planet' (assume it's a hash-map), return true if - it has at least one moon." +(defn analyze-planet [planet] - (> (:moons planet) - 0)) - -(planet-has-moons? {:pname "Mercury" :moons 0}) - -(planet-has-moons? {:pname "Earth" :moons 1}) - -(planet-has-moons? {:pname "Mars" :moons 2}) - + (cond + (habitable? planet) :inhabit + (colonisable? planet) :colonise + (observe-only? planet) :observe-only + :else :send-probes)) -;; EXERCISE -;; -;; Instead of querying each map, why not query all of them at one go? -#_(map 'FIX 'FIX) +#_(map (juxt :pname analyze-planet) + p/target-planets) +(map analyze-planet p/target-planets) -;; Also, as we now know, we can use anonymous functions creatively... +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Starfleet assigns mission vessels... +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; EXERCISE -;; -;; Replace 'FIX with your own anonymous function that works -;; just like `planet-has-moons?`. -#_(map 'FIX planets) +(defmulti assign-vessels ; multi-method definition + analyze-planet) ; dispatch function -;; EXERCISE -;; -;; And, finally, prove that both variants do exactly the same thing: +(defmethod assign-vessels :inhabit [planet] + (assoc planet + :vessels {:starships 5, :orbiters 5, :probes 100, + :battle-cruisers 5, :cargo-ships 5})) -#_(= (map planet-has-moons? 'FIX) - ('FIX 'FIX 'FIX) ; use anonymous function +(defmethod assign-vessels :colonise [planet] + (assoc planet + :vessels {:starships 1, :probes 50})) - [false false true true]) +(defmethod assign-vessels :send-probes [planet] + (assoc planet + :vessels {:orbiters 1, :probes 10})) -;; RECAP: -;; - Functions are easy to define -;; - Functions can _accept_ functions as arguments -;; - Functions can _return_ functions as arguments -;; - Clojure has nifty "built-in" functions -;; - e.g. the function `map` lets us express a mapping of an -;; input collection to an output collection, by way of a function. +(defmethod assign-vessels :observe-only [planet] + (assoc planet + :vessels {:orbiters 1})) -;; "Do more with less": -;; - Even the most simple functions can be general-purpose -;; - Clojure keywords behave like functions of hash-maps -;; - Since keywords can query hash-maps, we can completely avoid writing -;; custom "getter" functions to query values in our hash-maps. -;; - We can put hash-maps in vectors, to make vectors of hash-maps. -;; - Since `map` accepts keywords in place of functions, we can -;; combine the above tiny set of ideas, to query many planetary hash- -;; maps at one go. +#_(map (juxt :pname analyze-planet :vessels) + (map assign-vessels p/target-planets)) From e895bb08953c72d40660288f53a604e26ea20a69 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Thu, 25 Oct 2018 21:29:40 +0530 Subject: [PATCH 04/62] Extract functions, lexical scope from ex02 to ex01 --- src/clojure_by_example/ex00_introduction.clj | 323 ----------------- .../ex01_fundamentally_functional.clj | 326 ++++++++++++++++++ ...in_as_data.clj => ex02_domain_as_data.clj} | 4 +- 3 files changed, 328 insertions(+), 325 deletions(-) create mode 100644 src/clojure_by_example/ex01_fundamentally_functional.clj rename src/clojure_by_example/{ex01_domain_as_data.clj => ex02_domain_as_data.clj} (99%) diff --git a/src/clojure_by_example/ex00_introduction.clj b/src/clojure_by_example/ex00_introduction.clj index a4ef07c..15e4422 100644 --- a/src/clojure_by_example/ex00_introduction.clj +++ b/src/clojure_by_example/ex00_introduction.clj @@ -13,9 +13,6 @@ ;; to follow (and generally, to help grok code in the wild) ;; - Familiarize one's eyes with Clojure syntax ;; - Understand Clojure's evaluation model -;; - Introduce a handful of important concepts, -;; data structures, expressions, and functions -;; that are used pervasively. ;; - Start using an interactive development workflow ;; right away @@ -187,323 +184,3 @@ same ; is defined in the current ns ;; - [4] is a Clojure s-expression, and is treated as the body of ;; the function definition ;; - [5] the whole thing itself is a Clojure s-expression! - - - -;; Basic Function Syntax -;; -;; - Named functions: -;; -(defn function-name - "Documentation string (optional)." - [arg1 arg2 arg3 etc up to argN] - 'function 'body - 'goes 'here - '...) - -;; - Anonymous functions: -;; -(fn [arg1 arg2 arg3 etc up to argN] - 'function 'body - 'goes 'here - '...) - - - -;; A dead-simple function: - -(defn same - "Simply return the input unchanged." - [x] - x) - - -(fn [x] x) ; just like `same`, but with no name - - - -;; EXERCISE -;; -;; Evaluate and see: - -(same 42) - -(same [1 2 3 4 5]) - -(same {:pname "Earth" :moons 1}) - - -;; How about the anonymous version of `same`? -;; - What's the evaluation model? - -((fn [x] x) 42) - -((fn [x] x) [1 2 3 4 5]) - -((fn [x] x) {:pname "Earth" :moons 1}) - - -;; What should happen here? -(= 'FIX - (same same) - ((fn [x] x) same)) - - -;; `identity` -;; - provided by Clojure -;; - is exactly like our`same` function -;; - is extremely general (accepts any value) -;; - is surprisingly useful, as we will discover later - -;; Fix this to prove `identity` and `same` are the same: -;; - Note: Functions are values and can therefore be compared. -;; -(= identity - (same identity) - ((fn [x] x) identity) - (identity identity)) -;; -;; Now, evaluate this in the REPL to _see_ the truth: -;; (clojure.repl/source identity) - - - -;; And to round it up... functions can accept, as well as -;; return functions. - -(defn gen-identity - [] ; zero arguments - identity) - - -(= identity - (gen-identity)) - - -(defn selfie - "Given a function `f`, return the result of - applying `f` to itself." - [f] - (f f)) - - -(= 42 - (identity 42) - ((selfie identity) 42) - ((selfie (selfie identity)) 42) - ((selfie (selfie (selfie identity))) 42)) ; ad-infinitum - - -;; Compose (chain) functions with `comp` -((comp inc inc inc) 39) - -;; Negate predicates with `complement` -((complement string?) "hi") - -;; Use `fnil` to "nil-patch" functions that cannot sanely handle nil (null) inputs -#_(+ nil 1) ; FAIL -((fnil + 0) nil 1) ; OK - -;; Juxtapose functions with `juxt` (place results "side-by-side") -((juxt inc identity dec) 42) - - - -;; Lexical Scope in Clojure - - -;; EXERCISE: -;; Reason about the following expressions. -;; - Mentally evaluate and predict the results; then check. - -(def x 42) ; Bind `x` to 42, globally ("top-level" binding) - -(identity x) ; obviously returns 42 - -((fn [x] x) x) ; also returns 42, but how? - -(let [x 10] ; we use `let` to bind things locally - (+ x 1)) - - -;; EXERCISE: -;; Read carefully, and compare these three function variants: - -(defn add-one-v1 - [x] - (+ x 1)) ; which `x` will this `x` reference? - -(add-one-v1 1) ; should evaluate to what? -(add-one-v1 x) ; should evaluate to what? - - -(defn add-one-v2 - [z] - (+ x 1)) ; which `x` will this `x` reference? - -(add-one-v2 1) ; should evaluate to what? -(add-one-v2 x) ; should evaluate to what? - - -(defn add-one-v3 - [x] - (let [x 10] - (+ x 1))) ; which `x` will this `x` reference? - -(add-one-v3 1) ; should evaluate to what? -(add-one-v3 x) ; should evaluate to what? - - -;; `let` -;; - Mentally evaluate these, predict the results, -;; and try to infer the scoping rule. -;; - Start with any `x`, and mechanically work -;; your way around. - -((fn [x] x) (let [x 10] x)) - - -((fn [x] x) (let [x x] x)) - - -(let [x 10] ((fn [x] x) x)) - - -((let [x 10] (fn [x] x)) x) - - - -;; Function Closure: -;; - Read carefully and work out what gets bound to `scale-by-PI`. - -(defn scale-by - "Given a number `x`, return a function that accepts - another number `y`, and scales `y` by `x`." - [x] - (fn [y] (* y x))) - -(def PI 3.141592653589793) - -((scale-by PI) 10) - - -;; Achieve the same with `partial` application -(def scale-by-PI (partial * PI)) - -(scale-by-PI 10) - - -;; Strict lexical scope allows us to mechanically work out -;; where a value originated. -;; - Start at the place of reference of the value. -;; - Then "walk" outwards, until you meet the very first let binding, -;; or argument list, or def, where the value was bound. -;; - Now you know where the value came from. - - - -;; Sequences (or Collections), and operations on Sequences -;; - Clojure provides _many_ sequence functions. -;; Here are some important ones: - -map -;; Basic Syntax: -;; -;; (map a-function a-collection) -;; -;; Where the function must accept exactly one argument, because -;; it must transform only one item of the input at a time. - -(map inc [1 2 3 4 5 6]) -;; | | | | | | ; declare a mapping of each item of the input coll -;; inc inc inc ; via `inc` -;; | | | | | | -;; (2 3 4 5 6 7) ; to each item of the output coll - - -filter -;; Basic Syntax: -;; -;; (filter a-predicate-fn a-collection) -;; -;; Where the function must accept exactly one argument and -;; return a truthy result (hence we term it a "predicate" function). - -(filter even? [1 2 3 4 5 6]) - -(filter identity [1 nil 3 nil 5 nil]) ; nil is falsey, non-nils are truthy - - -reduce -;; Basic Syntax: -;; -;; (reduce a-function accumulator a-collection) -;; -;; Where the function must accept two arguments: -;; - first one is the value of the accumulator it manages, and -;; - the second one is bound to each item of the collection - -(reduce + 0 [0 0 1 2]) - -;; Imagine each step of the above computation, like this: - -;; ======================================= -;; Accumulator | Input collection (of number of moons) -;; ======================================= -;; 0 (start) | [0 0 1 2] ; just before first step -;; 0 | [0 1 2] ; at end of first step -;; 0 | [1 2] -;; 1 | [2] -;; 3 | [] ; reduce detects empty collection -;; --------------------------------------- -;; 3 (return value) ; reduce spits out the accumulator - - - -;; Truthiness -;; -;; - Only `nil` and `false` are Falsey; everything else -;; is Truthy -;; - a "predicate" function can return Truthy/Falsey, -;; not just boolean true/false -;; - we can make good use of this behaviour, in Clojure - - -(def a-bunch-of-values - [nil, false, ; falsey - 42, :a, "foo", true, ; truthy - {:a 1, :b 2}, [1 2 3 4], ; truthy - '(), {}, [], ""]) ; truthy - - -;; A quick proof: -(map boolean ; coerces a given value to boolean true or false - a-bunch-of-values) - -(filter boolean - a-bunch-of-values) - - -;; Branching logic accepts Truthy/Falsey - -(if nil ; if condition is Truthy - "hi!" ; then evaluate the first expression - "boo!") ; else evaluate the second expression - - -(when false ; only when the condition is truthy - "boo!") ; evaluate the body. Otherwise, always return `nil` - - - -;; RECAP: -;; -;; - All Clojure code is a bunch of "expressions" -;; (literals, collections, s-expressions) -;; -;; - All Clojure expressions evaluate to a return value -;; -;; - All Clojure code is written in terms of its own data structures -;; -;; - All opening braces or parentheses must be matched by closing -;; braces or parentheses, to create legal Clojure expressions. diff --git a/src/clojure_by_example/ex01_fundamentally_functional.clj b/src/clojure_by_example/ex01_fundamentally_functional.clj new file mode 100644 index 0000000..e53b651 --- /dev/null +++ b/src/clojure_by_example/ex01_fundamentally_functional.clj @@ -0,0 +1,326 @@ +(ns clojure-by-example.ex01-fundamentally-functional) + +;; EX01: LESSON GOAL: +;; - Realize that pure functions, and strict lexical scope +;; are the bedrock upon which Clojure programs are built +;; - Drill how to use functions, and how lexical scope works +;; - Get comfortable with how functions compose together + + +;; Basic Function Syntax +;; +;; - Named functions: +;; +(defn function-name + "Documentation string (optional)." + [arg1 arg2 arg3 etc up to argN] + 'function 'body + 'goes 'here + '...) + +;; - Anonymous functions: +;; +(fn [arg1 arg2 arg3 etc up to argN] + 'function 'body + 'goes 'here + '...) + + + +;; A dead-simple function: + +(defn same + "Simply return the input unchanged." + [x] + x) + + +(fn [x] x) ; just like `same`, but with no name + + + +;; EXERCISE +;; +;; Evaluate and see: + +(same 42) + +(same [1 2 3 4 5]) + +(same {:pname "Earth" :moons 1}) + + +;; How about the anonymous version of `same`? +;; - What's the evaluation model? + +((fn [x] x) 42) + +((fn [x] x) [1 2 3 4 5]) + +((fn [x] x) {:pname "Earth" :moons 1}) + + +;; What should happen here? +(= 'FIX + (same same) + ((fn [x] x) same)) + + +;; `identity` +;; - provided by Clojure +;; - is exactly like our`same` function +;; - is extremely general (accepts any value) +;; - is surprisingly useful, as we will discover later + +;; Fix this to prove `identity` and `same` are the same: +;; - Note: Functions are values and can therefore be compared. +;; +(= identity + (same identity) + ((fn [x] x) identity) + (identity identity)) +;; +;; Now, evaluate this in the REPL to _see_ the truth: +;; (clojure.repl/source identity) + + + +;; And to round it up... functions can accept, as well as +;; return functions. + +(defn gen-identity + [] ; zero arguments + identity) + + +(= identity + (gen-identity)) + + +(defn selfie + "Given a function `f`, return the result of + applying `f` to itself." + [f] + (f f)) + + +(= 42 + (identity 42) + ((selfie identity) 42) + ((selfie (selfie identity)) 42) + ((selfie (selfie (selfie identity))) 42)) ; ad-infinitum + + +;; Compose (chain) functions with `comp` +((comp inc inc inc) 39) + +;; Negate predicates with `complement` +((complement string?) "hi") + +;; Use `fnil` to "nil-patch" functions that cannot sanely handle nil (null) inputs +#_(+ nil 1) ; FAIL +((fnil + 0) nil 1) ; OK + +;; Juxtapose functions with `juxt` (place results "side-by-side") +((juxt inc identity dec) 42) + + + +;; Lexical Scope in Clojure + + +;; EXERCISE: +;; Reason about the following expressions. +;; - Mentally evaluate and predict the results; then check. + +(def x 42) ; Bind `x` to 42, globally ("top-level" binding) + +(identity x) ; obviously returns 42 + +((fn [x] x) x) ; also returns 42, but how? + +(let [x 10] ; we use `let` to bind things locally + (+ x 1)) + + +;; EXERCISE: +;; Read carefully, and compare these three function variants: + +(defn add-one-v1 + [x] + (+ x 1)) ; which `x` will this `x` reference? + +(add-one-v1 1) ; should evaluate to what? +(add-one-v1 x) ; should evaluate to what? + + +(defn add-one-v2 + [z] + (+ x 1)) ; which `x` will this `x` reference? + +(add-one-v2 1) ; should evaluate to what? +(add-one-v2 x) ; should evaluate to what? + + +(defn add-one-v3 + [x] + (let [x 10] + (+ x 1))) ; which `x` will this `x` reference? + +(add-one-v3 1) ; should evaluate to what? +(add-one-v3 x) ; should evaluate to what? + + +;; `let` +;; - Mentally evaluate these, predict the results, +;; and try to infer the scoping rule. +;; - Start with any `x`, and mechanically work +;; your way around. + +((fn [x] x) (let [x 10] x)) + + +((fn [x] x) (let [x x] x)) + + +(let [x 10] ((fn [x] x) x)) + + +((let [x 10] (fn [x] x)) x) + + + +;; Function Closure: +;; - Read carefully and work out what gets bound to `scale-by-PI`. + +(defn scale-by + "Given a number `x`, return a function that accepts + another number `y`, and scales `y` by `x`." + [x] + (fn [y] (* y x))) + +(def PI 3.141592653589793) + +((scale-by PI) 10) + + +;; Achieve the same with `partial` application +(def scale-by-PI (partial * PI)) + +(scale-by-PI 10) + + +;; Strict lexical scope allows us to mechanically work out +;; where a value originated. +;; - Start at the place of reference of the value. +;; - Then "walk" outwards, until you meet the very first let binding, +;; or argument list, or def, where the value was bound. +;; - Now you know where the value came from. + + + +;; Sequences (or Collections), and operations on Sequences +;; - Clojure provides _many_ sequence functions. +;; Here are some important ones: + +map +;; Basic Syntax: +;; +;; (map a-function a-collection) +;; +;; Where the function must accept exactly one argument, because +;; it must transform only one item of the input at a time. + +(map inc [1 2 3 4 5 6]) +;; | | | | | | ; declare a mapping of each item of the input coll +;; inc inc inc ; via `inc` +;; | | | | | | +;; (2 3 4 5 6 7) ; to each item of the output coll + + +filter +;; Basic Syntax: +;; +;; (filter a-predicate-fn a-collection) +;; +;; Where the function must accept exactly one argument and +;; return a truthy result (hence we term it a "predicate" function). + +(filter even? [1 2 3 4 5 6]) + +(filter identity [1 nil 3 nil 5 nil]) ; nil is falsey, non-nils are truthy + + +reduce +;; Basic Syntax: +;; +;; (reduce a-function accumulator a-collection) +;; +;; Where the function must accept two arguments: +;; - first one is the value of the accumulator it manages, and +;; - the second one is bound to each item of the collection + +(reduce + 0 [0 0 1 2]) + +;; Imagine each step of the above computation, like this: + +;; ======================================= +;; Accumulator | Input collection (of number of moons) +;; ======================================= +;; 0 (start) | [0 0 1 2] ; just before first step +;; 0 | [0 1 2] ; at end of first step +;; 0 | [1 2] +;; 1 | [2] +;; 3 | [] ; reduce detects empty collection +;; --------------------------------------- +;; 3 (return value) ; reduce spits out the accumulator + + + +;; Truthiness +;; +;; - Only `nil` and `false` are Falsey; everything else +;; is Truthy +;; - a "predicate" function can return Truthy/Falsey, +;; not just boolean true/false +;; - we can make good use of this behaviour, in Clojure + + +(def a-bunch-of-values + [nil, false, ; falsey + 42, :a, "foo", true, ; truthy + {:a 1, :b 2}, [1 2 3 4], ; truthy + '(), {}, [], ""]) ; truthy + + +;; A quick proof: +(map boolean ; coerces a given value to boolean true or false + a-bunch-of-values) + +(filter boolean + a-bunch-of-values) + + +;; Branching logic accepts Truthy/Falsey + +(if nil ; if condition is Truthy + "hi!" ; then evaluate the first expression + "boo!") ; else evaluate the second expression + + +(when false ; only when the condition is truthy + "boo!") ; evaluate the body. Otherwise, always return `nil` + + + +;; RECAP: +;; +;; - All Clojure code is a bunch of "expressions" +;; (literals, collections, s-expressions) +;; +;; - All Clojure expressions evaluate to a return value +;; +;; - All Clojure code is written in terms of its own data structures +;; +;; - All opening braces or parentheses must be matched by closing +;; braces or parentheses, to create legal Clojure expressions. diff --git a/src/clojure_by_example/ex01_domain_as_data.clj b/src/clojure_by_example/ex02_domain_as_data.clj similarity index 99% rename from src/clojure_by_example/ex01_domain_as_data.clj rename to src/clojure_by_example/ex02_domain_as_data.clj index a07d14f..fa02f9d 100644 --- a/src/clojure_by_example/ex01_domain_as_data.clj +++ b/src/clojure_by_example/ex02_domain_as_data.clj @@ -1,7 +1,7 @@ -(ns clojure-by-example.ex01-domain-as-data) +(ns clojure-by-example.ex02-domain-as-data) -;; Ex01: LESSON GOAL: +;; Ex02: LESSON GOAL: ;; - Model and query things using pure data ;; - See how to "de-structure" data (it's a powerful, flexible lookup mechanism) ;; - Leverage de-structuring to design a self-documenting function API From 28f6cf3e5fe5c8c44027573c9545858706d0461f Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Thu, 25 Oct 2018 21:31:57 +0530 Subject: [PATCH 05/62] Replace ex03 with totally refactored variant --- .../ex02_small_functions.clj | 278 ---------- .../ex03_data_and_functions.clj | 510 +++++++----------- 2 files changed, 188 insertions(+), 600 deletions(-) delete mode 100644 src/clojure_by_example/ex02_small_functions.clj diff --git a/src/clojure_by_example/ex02_small_functions.clj b/src/clojure_by_example/ex02_small_functions.clj deleted file mode 100644 index 242ec5a..0000000 --- a/src/clojure_by_example/ex02_small_functions.clj +++ /dev/null @@ -1,278 +0,0 @@ -(ns clojure-by-example.ex02-small-functions ; current namespace (ns) - ;; "require" and alias another ns as `p`: - (:require [clojure-by-example.data.planets :as p])) - - -;; Ex02: LESSON GOALS -;; - Use stuff we've seen so far to build purely functional logic -;; to process a bunch of planets - - -;; Here are some target planets: -clojure-by-example.data.planets/target-planets - -;; Which we can access more conveniently as: -p/target-planets - -(map :pname p/target-planets) - -;; Now, let's colonize planets! - - -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Some constants and utilities -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -(def tolerances - "Define low/high bounds of planetary characteristics we care about." - {:co2 {:low 0.1, :high 5.0} - :gravity {:low 0.1, :high 2.0} - :surface-temp-deg-c {:low -125, :high 60}}) - - -(defn lower-bound - [tolerance-key] - (get-in tolerances [tolerance-key :low])) - - -(defn upper-bound - [tolerance-key] - (get-in tolerances [tolerance-key :high])) - - -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Functions to test a planet for particular conditions -;; - We make them return truthy/falsey values -;; - We call such functions "predicates" -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn atmosphere-present? - [{:keys [atmosphere] :as planet}] - (not-empty atmosphere)) - -#_(map :pname - (filter atmosphere-present? p/target-planets)) - - -(defn co2-tolerable? - [{{:keys [carbon-dioxide] - :or {carbon-dioxide 0.0}} :atmosphere - :as planet}] - (<= (lower-bound :co2) - carbon-dioxide - (upper-bound :co2))) - -#_(map :pname - (filter co2-tolerable? p/target-planets)) - - -(defn gravity-tolerable? - [{:keys [gravity] :as planet}] - (when gravity - (<= (lower-bound :gravity) - gravity - (upper-bound :gravity)))) - -#_(map :pname - (filter gravity-tolerable? p/target-planets)) - - -(defn surface-temp-tolerable? - [{{:keys [low high]} :surface-temp-deg-c - :as planet}] - (when (and low high) - (<= (lower-bound :surface-temp-deg-c) - low - high - (upper-bound :surface-temp-deg-c)))) - -#_(map :pname - (filter surface-temp-tolerable? p/target-planets)) - - -(def poison-gas? - "A set of poison gases." - #{:chlorine, :sulphur-dioxide, :carbon-monoxide}) - - - -;; We can use sets as predicates. Sets behave as functions -;; that test for set membership. - -(poison-gas? :oxygen) ; falsey - -(poison-gas? :chlorine) ; truthy - - -(defn air-too-poisonus? - [{:keys [atmosphere] :as planet}] - (some (fn [[gas-name gas-pct]] - (and (poison-gas? gas-name) - (<= 1.0 gas-pct))) - atmosphere)) - -(map :pname - (filter air-too-poisonus? p/target-planets)) - -;; a hash-map is a collection of key-value pairs -(map identity - {:nitrogen 78.08, :oxygen 20.95, :carbon-dioxide 0.4, - :water-vapour 0.1, :argon 0.33, :traces 0.14}) - -(map (fn [[k v]] (str v "% " k)) - {:nitrogen 78.08, :oxygen 20.95, :carbon-dioxide 0.4, - :water-vapour 0.1, :argon 0.33, :traces 0.14}) - - - -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Composite checks to -;; - test whether a given planet meets a variety of conditions. -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -(defn planetary-conditions-meet-constraint? - "Return the given planet as-is, when it meets the condition fns, - as per the constraint fn (like every?, some, not-every). - - The constraint fn must return a Boolean true/false value." - [constraint] - (fn [conditions planet] - (when (true? (constraint (fn [f] (f planet)) - conditions)) - planet))) - - -(def planet-meets-any-one-condition? - (planetary-conditions-meet-constraint? - some)) - - -(def planet-meets-all-conditions? - (planetary-conditions-meet-constraint? - every?)) - - -(def planet-meets-no-condition? - (planetary-conditions-meet-constraint? - not-any?)) - - -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Conditions for colonisation -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -(def minimal-good-conditions - "A collection of functions that tell us about the - good-ness of planetary conditions." - [co2-tolerable? - gravity-tolerable? - surface-temp-tolerable?]) - - -(def fatal-conditions - "A collection of functions that tell us about the - fatality of planetary conditions." - [(complement atmosphere-present?) - air-too-poisonus?]) - - -(defn habitable? - "We deem a planet habitable, if it has all minimally good conditions, - and no fatal conditions." - [planet] - (planet-meets-no-condition? - fatal-conditions - (planet-meets-all-conditions? - minimal-good-conditions - planet))) - - -#_(map :pname - (filter habitable? p/target-planets)) - - -(defn colonisable? - "We deem a planet colonisable, if it has at least one - minimally good condition, and no fatal conditions." - [planet] - (and (planet-meets-any-one-condition? - minimal-good-conditions - planet) - (planet-meets-no-condition? - fatal-conditions - planet))) - -#_(map :pname - (filter colonisable? p/target-planets)) - - -(defn observe-only? - "We select a planet for orbital observation, if it only has harsh surface conditions." - [planet] - (and (planet-meets-any-one-condition? - fatal-conditions - planet) - (planet-meets-no-condition? - minimal-good-conditions - planet))) - -#_(map :pname - (filter observe-only? p/target-planets)) - - - -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Enrich planetary data with analytical results -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -(defn analyze-planet - [planet] - (cond - (habitable? planet) :inhabit - (colonisable? planet) :colonise - (observe-only? planet) :observe-only - :else :send-probes)) - - -#_(map (juxt :pname analyze-planet) - p/target-planets) - -(map analyze-planet p/target-planets) - - -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Starfleet assigns mission vessels... -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -(defmulti assign-vessels ; multi-method definition - analyze-planet) ; dispatch function - - -(defmethod assign-vessels :inhabit [planet] - (assoc planet - :vessels {:starships 5, :orbiters 5, :probes 100, - :battle-cruisers 5, :cargo-ships 5})) - - -(defmethod assign-vessels :colonise [planet] - (assoc planet - :vessels {:starships 1, :probes 50})) - - -(defmethod assign-vessels :send-probes [planet] - (assoc planet - :vessels {:orbiters 1, :probes 10})) - - -(defmethod assign-vessels :observe-only [planet] - (assoc planet - :vessels {:orbiters 1})) - - -#_(map (juxt :pname analyze-planet :vessels) - (map assign-vessels p/target-planets)) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index 6549b41..82cec06 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -1,412 +1,278 @@ -(ns clojure-by-example.ex03-data-and-functions) +(ns clojure-by-example.ex03-data-and-functions ; current namespace (ns) + ;; "require" and alias another ns as `p`: + (:require [clojure-by-example.data.planets :as p])) -;; Ex03: LESSON GOALS (build upon ex01, and ex02) -;; -;; - Learn a few more nifty functions on collections (cousins of `map`) -;; - Learn to combine simple functions into more powerful functions -;; - Get a sense of how to model with data, and compute with functions -;; - Get a feel for some of the flexibility of Clojure data structures +;; Ex03: LESSON GOALS +;; - Use stuff we've seen so far to build purely functional logic +;; to process a bunch of planets -;; Let's begin again, with our planets, our moons checker, and `map` -(def planets [{:name "Mercury" :moons 0 :mass 0.0533} - {:name "Venus" :moons 0 :mass 0.815} - {:name "Earth" :moons 1 :mass 1} - {:name "Mars" :moons 2 :mass 0.107} - {:name "Jupiter" :moons 69 :mass 317.8} - {:name "Saturn" :moons 62 :mass 95.2}]) +;; Here are some target planets: +clojure-by-example.data.planets/target-planets +;; Which we can access more conveniently as: +p/target-planets -;; Recall: Our little "helper" function, to check if a planet has moons. -(defn planet-with-moons? - "Given a 'planet' (assume it's a hash-map), return true if - it has at least one moon." - [planet] - (> (:moons planet) - 0)) - -;; The following results make some sense: - -(map :name planets) ; ok, we can now print just the names of planets - -(map :moons planets) ; ok, we can now count total number of moons - - -;; But what do we do with a bunch of boolean results? - -(map planet-with-moons? planets) - - -;; More usefully, I would like to know _which_ planets have moons. -;; -;; Even more usefully, given a collection of planets, I want to -;; filter all planets with moons. - - -;; EXERCISE: -;; -;; Predict the result of this expression: - -(filter planet-with-moons? - planets) - - -;; Can we do the opposite? - -(filter (fn [p] (not (planet-with-moons? p))) - planets) - - -;; EXERCISE -;; -;; You can define a new "helper" function, in terms of earlier helper -;; functions... Fix the function body, and make it work. - -(defn planet-without-moons? - "Planets without moons are exactly opposite to planets with moons." - [FIX] - FIX) - - -;; EXERCISE -;; -;; Now, filter out planets without moons... +(map :pname p/target-planets) -;; (FIX FIX FIX) ; fixme and evaluate +;; Now, let's colonize planets! +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Some constants and utilities +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Can we write a function to filter planets into two groups? -;; - planets with moons, and -;; - planets without moons? -;; EXERCISE: -;; -;; Will this work? Why? -;; - Take a guess... and then evaluate to check. -;; -{:planets-with-moons (filter planet-with-moons? planets) - :planets-without-moons (filter planet-without-moons? planets)} +(def tolerances + "Define low/high bounds of planetary characteristics we care about." + {:co2 {:low 0.1, :high 5.0} + :gravity {:low 0.1, :high 2.0} + :surface-temp-deg-c {:low -125, :high 60}}) -;; Note: Do More With Less -;; -;; - Clojure lets us simply write down the structure of hash-maps, -;; even if some values need to be computed. No special constructor -;; required. -;; -;; - If an s-expression like (filter ...) is associated with a key, -;; you may imagine that Clojure will replace the s-expression -;; with the result of evaluating that expression, if we try to -;; evaluate the whole hash-map at one go. -;; -;; By the way it's not just hash-maps, this works for vectors too, -;; for the same reason: +(defn lower-bound + [tolerance-key] + (get-in tolerances [tolerance-key :low])) -[(filter planet-with-moons? planets) - (filter planet-without-moons? planets)] +(defn upper-bound + [tolerance-key] + (get-in tolerances [tolerance-key :high])) -;; EXERCISE: -;; -;; So now, to group planets by moons, we can... do what? -(defn group-planets-by-moons - [planets] - ;; FIX: return a data structure here that represents the grouping. - 'FIX) +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions to test a planet for particular conditions +;; - We make them return truthy/falsey values +;; - We call such functions "predicates" +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn atmosphere-present? + [{:keys [atmosphere] :as planet}] + (not-empty atmosphere)) -;; EXERCISE: -;; -;; Use `group-planets-by-moons` to group planets. -;; Write your solution below: +#_(map :pname + (filter atmosphere-present? p/target-planets)) -#_('FIX 'FIX) -;; If you did it right, this is what happened: -;; -;; - When we called the function with `planets`, it did this: -;; - evaluated each filter expression one by one -;; - put the results in the respective places in the hash-map -;; - returned the whole hash-map +(defn co2-tolerable? + [{{:keys [carbon-dioxide] + :or {carbon-dioxide 0.0}} :atmosphere + :as planet}] + (<= (lower-bound :co2) + carbon-dioxide + (upper-bound :co2))) +#_(map :pname + (filter co2-tolerable? p/target-planets)) -;; Now we can further find... -(:planets-with-moons (group-planets-by-moons planets)) +(defn gravity-tolerable? + [{:keys [gravity] :as planet}] + (when gravity + (<= (lower-bound :gravity) + gravity + (upper-bound :gravity)))) +#_(map :pname + (filter gravity-tolerable? p/target-planets)) -;; EXERCISE: -;; -;; Find names of those planets that have moons... +(defn surface-temp-tolerable? + [{{:keys [low high]} :surface-temp-deg-c + :as planet}] + (when (and low high) + (<= (lower-bound :surface-temp-deg-c) + low + high + (upper-bound :surface-temp-deg-c)))) -#_('FIX 'FIX 'FIX) +#_(map :pname + (filter surface-temp-tolerable? p/target-planets)) -;; So far, we did some pretty cool things with sequence operations like -;; `map` and `filter`. Now for the big boss of sequence operations... +(def poison-gas? + "A set of poison gases." + #{:chlorine, :sulphur-dioxide, :carbon-monoxide}) -;; REDUCE! +;; We can use sets as predicates. Sets behave as functions +;; that test for set membership. -;; Remember we can collect moons from planets? +(poison-gas? :oxygen) ; falsey -(map :moons planets) +(poison-gas? :chlorine) ; truthy -;; Now, how to "reduce" the collection into one number? -;; i.e., how to count the sum total of moons of all given planets? +(defn air-too-poisonus? + [{:keys [atmosphere] :as planet}] + (some (fn [[gas-name gas-pct]] + (and (poison-gas? gas-name) + (<= 1.0 gas-pct))) + atmosphere)) -;; Well... +(map :pname + (filter air-too-poisonus? p/target-planets)) -(reduce + 0 (map :moons planets)) -;; It computes this: 0 + p1-moons + p2-moons + .... + pN-moons +;; a hash-map is a collection of key-value pairs +(map identity + {:nitrogen 78.08, :oxygen 20.95, :carbon-dioxide 0.4, + :water-vapour 0.1, :argon 0.33, :traces 0.14}) -;; `reduce` takes a function, an "accumulator" value, and an input -;; collection, and returns an "accumulated" value. +(map (fn [[k v]] (str v "% " k)) + {:nitrogen 78.08, :oxygen 20.95, :carbon-dioxide 0.4, + :water-vapour 0.1, :argon 0.33, :traces 0.14}) -;; Imagine each step of the above computation, like this: -;; ======================================= -;; Accumulator | Input collection (of number of moons) -;; ======================================= -;; 0 (start) | [0 0 1 2] ; just before first step -;; 0 | [0 1 2] ; at end of first step -;; 0 | [1 2] -;; 1 | [2] -;; 3 | [] ; reduce detects empty collection -;; --------------------------------------- -;; 3 (return value) ; reduce spits out the accumulator -;; In words... -;; -;; - To begin with, `reduce` passes the accumulator and the first item -;; from the input collection to the function. -;; -;; - `reduce` "manages" the accumulator for us, such that the function -;; can update the accumulator, and `reduce` will feed the updated -;; accumulator back into the function, along with the next item -;; in the input collection. -;; -;; - This continues until the input collection is exhausted. +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Composite checks to +;; - test whether a given planet meets a variety of conditions. +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; `reductions` is a convenience function that helps us visualize -;; the "accumulator" at each step of the `reduce` computation: -(reductions + 0 [0 0 1 2]) ; compare with the table +(defn planetary-conditions-meet-constraint? + "Return the given planet as-is, when it meets the condition fns, + as per the constraint fn (like every?, some, not-every). -(reductions + 0 (map :moons planets)) ; calculate total number of moons + The constraint fn must return a Boolean true/false value." + [constraint] + (fn [conditions planet] + (when (true? (constraint (fn [f] (f planet)) + conditions)) + planet))) -(reductions + 2 (map :moons planets)) ; accumulator can be any number +(def planet-meets-any-one-condition? + (planetary-conditions-meet-constraint? + some)) -;; Lesson-end Exercises: -;; IMPORTANT: -;; -;; - Don't refer back to the code above unless you're _really_ stuck. -;; -;; - Try to reason from first principles - use the basic ideas we have -;; acquired so far. -;; -;; - Write your own functions _from scratch_, if you need them. +(def planet-meets-all-conditions? + (planetary-conditions-meet-constraint? + every?)) -;; EXERCISE: -;; -;; Calculate the total mass of all `planets` -;; - Hint: this will be a one-liner s-expression -;; - Write your solution below: +(def planet-meets-no-condition? + (planetary-conditions-meet-constraint? + not-any?)) -;; EXERCISE: -;; -;; Count the number of `planets` that have moons. -;; - Reuse the `planet-with-moons?` function that we already defined. -;; - Use the `count` function to find the counts. +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Conditions for colonisation +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(count [42 43 44 45]) ; try this example -;; Write your solution below: +(def minimal-good-conditions + "A collection of functions that tell us about the + good-ness of planetary conditions." + [co2-tolerable? + gravity-tolerable? + surface-temp-tolerable?]) +(def fatal-conditions + "A collection of functions that tell us about the + fatality of planetary conditions." + [(complement atmosphere-present?) + air-too-poisonus?]) -;; EXERCISE: -;; -;; Calculate the total mass of planets having moons. -;; - Reuse the `planet-with-moons?` function that we already defined. - - - -;; EXERCISE: -;; -;; Calculate the total mass of planets _without_ moons. -;; - Reuse the `planet-without-moons?` function that we defined earlier. - - - -;; EXERCISE: -;; -;; Write a function `massier-than-earth?` which, -;; - given a planet, -;; - returns true if the planet's mass exceeds Earth's mass -;; - returns false otherwise - - - -;; EXERCISE: -;; -;; Write a function `planetary-stats` that: -;; -;; Takes TWO arguments: -;; 1. a "predicate" function like `massier-than-earth?` -;; 2. a sequence of some planets, -;; -;; Returns the following stats about those input planets -;; that match the filter criteria: -;; - count of the planets -;; - names of the planets -;; - total mass of the planets -;; -;; Important: -;; - The return value must be, an easy-to-query data structure. - -;; Uncomment and fix: -#_(defn planetary-stats - [pred-fn given-planets] - ;; `let` is a way to define ("bind") function-local names to values - (let [filtered-planets 'FIX] - {:count 'FIX - :names 'FIX - :total-mass 'FIX})) - - - -;; EXERCISE: -;; -;; Calculate `planetary-stats` for: -;; -;; - planets with moons: - - -;; EXERCISE: -;; -;; Calculate `planetary-stats` for: -;; -;; - planets without moons: +(defn habitable? + "We deem a planet habitable, if it has all minimally good conditions, + and no fatal conditions." + [planet] + (planet-meets-no-condition? + fatal-conditions + (planet-meets-all-conditions? + minimal-good-conditions + planet))) -;; - planets without moons, using `complement`: -;; compare, understand, use: +#_(map :pname + (filter habitable? p/target-planets)) -(planet-with-moons? {:name "Earth" :moons 1}) -((complement planet-with-moons?) {:name "Earth" :moons 1}) +(defn colonisable? + "We deem a planet colonisable, if it has at least one + minimally good condition, and no fatal conditions." + [planet] + (and (planet-meets-any-one-condition? + minimal-good-conditions + planet) + (planet-meets-no-condition? + fatal-conditions + planet))) -;; Fix the expression below: +#_(map :pname + (filter colonisable? p/target-planets)) -#_(planetary-stats - 'FIX - planets) +(defn observe-only? + "We select a planet for orbital observation, if it only has harsh surface conditions." + [planet] + (and (planet-meets-any-one-condition? + fatal-conditions + planet) + (planet-meets-no-condition? + minimal-good-conditions + planet))) -;; EXERCISE: -;; -;; Calculate `planetary-stats' for: -;; -;; - planets with more mass than the earth: +#_(map :pname + (filter observe-only? p/target-planets)) -;; - planets with less mass than the earth, using `comp`: -;; compare, understand, use: -#_(massier-than-earth? {:name "Jupiter" :mass 317.8}) +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Enrich planetary data with analytical results +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -#_((comp not massier-than-earth?) {:name "Jupiter" :mass 317.8}) -;; Type your solution here: +(defn analyze-planet + [planet] + (cond + (habitable? planet) :inhabit + (colonisable? planet) :colonise + (observe-only? planet) :observe-only + :else :send-probes)) +#_(map (juxt :pname analyze-planet) + p/target-planets) -;; EXERCISE: -;; -;; Calculate `planetary-stats' for: -;; -;; - all `planets` (hint: use `identity`): +(map analyze-planet p/target-planets) +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Starfleet assigns mission vessels... +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; EXERCISE: -;; -;; Write a function `more-planetary-stats` that: -;; - Takes a sequence of planets, and -;; - Returns an easy-to-query data structure containing -;; the following stats: -;; -;; given planets -;; count -;; names -;; total mass -;; planets with moons -;; count -;; names -;; total mass -;; planets without moons -;; count -;; names -;; total mass -;; planets with more mass than earth -;; count -;; names -;; total mass -;; planets having less mass than earth -;; count -;; names -;; total mass +(defmulti assign-vessels ; multi-method definition + analyze-planet) ; dispatch function -;; Fix the `more-planetary-stats` function below: -(defn more-planetary-stats - [FIX] - {:given-planets FIX - :with-moons FIX - :without-moons FIX - :massier-than-earth FIX - :less-massy-than-earth FIX}) +(defmethod assign-vessels :inhabit [planet] + (assoc planet + :vessels {:starships 5, :orbiters 5, :probes 100, + :battle-cruisers 5, :cargo-ships 5})) +(defmethod assign-vessels :colonise [planet] + (assoc planet + :vessels {:starships 1, :probes 50})) -;; Check your results. Uncomment and evaluate: -(comment - (more-planetary-stats planets) - (more-planetary-stats (take 2 planets)) +(defmethod assign-vessels :send-probes [planet] + (assoc planet + :vessels {:orbiters 1, :probes 10})) - (more-planetary-stats (drop 2 planets))) +(defmethod assign-vessels :observe-only [planet] + (assoc planet + :vessels {:orbiters 1})) -;; RECAP: -;; -;; - `map`, `filter`, and `reduce` are powerful sequence-processing -;; functions. Clojure programmers use these heavily. -;; -;; - Clojure programmers define small functions that do one task well, -;; and then build up sophisticated solutions by combining many such -;; small functions. -;; -;; - We can directly write down the actual structure of a data structure, -;; and Clojure will evaluate any un-evaluated values at run-time, -;; and return us the same data structure, but with computed values. -;; -;; - Clojure data structures are very flexible, and allow us to -;; model almost any real-world object. Previously we put hash-maps -;; inside a sequence to represent a collection of planets. And, in -;; this exercise, we put sequences inside a hash-map to group -;; planets by moons. +#_(map (juxt :pname analyze-planet :vessels) + (map assign-vessels p/target-planets)) From 368bc96a1a9b9d63550cd82bda894ef061998f36 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 29 Oct 2018 20:06:24 +0530 Subject: [PATCH 06/62] Limit ex02 to domain as data, remove destructuring Add sections to convey some of the power and flexibility of collections - function semantics - openness / composability - ability to model any domain one can imagine --- .../ex02_domain_as_data.clj | 194 ++++++------------ 1 file changed, 63 insertions(+), 131 deletions(-) diff --git a/src/clojure_by_example/ex02_domain_as_data.clj b/src/clojure_by_example/ex02_domain_as_data.clj index fa02f9d..c74bafd 100644 --- a/src/clojure_by_example/ex02_domain_as_data.clj +++ b/src/clojure_by_example/ex02_domain_as_data.clj @@ -1,10 +1,8 @@ (ns clojure-by-example.ex02-domain-as-data) - ;; Ex02: LESSON GOAL: ;; - Model and query things using pure data -;; - See how to "de-structure" data (it's a powerful, flexible lookup mechanism) -;; - Leverage de-structuring to design a self-documenting function API +;; - Realize the flexibility and power of collections ;; Our Earth @@ -123,154 +121,88 @@ planets))) -;; "De-structuring" +;; Collections like Maps, Vectors, and Sets can behave like functions +;; - We normally don't use Vectors and Maps like this, but +;; we often use sets as predicate functions: -;; - Clojure lets us reach into data structures in arbitrary ways, -;; and extract multiple values in one go -;; -;; - We use this for clean lookups in `let` bindings, and -;; in function signatures, to design expressive APIs. +({:a "a", :b "b", :c "c"} :c) ; key-value lookup +(["a" "b" "c"] 0) ; index lookup -;; Positional De-structuring -;; -;; - Pull apart sequential, ordered data structures like -;; lists, vectors, and any other sequence with linear access -;; -;; - Follow the structure of the collection, and mechanically -;; bind values to symbols by position. - - -;; Instead of looking up values by index position: -(str "The first two planets: " - (:pname (get planets 0)) " and " - (:pname (get planets 1)) ".") - +(#{"a" "b" "c"} "b") ; set membership -;; We can bind symbols by position (match structure to structure): -(let [[m, v] planets] - (str "The first two planets: " - (:pname m) " and " - (:pname v) ".")) +(def poison-gas? + "Does the given gas belong to a set of known poison gases?" + #{:carbon-monoxide, :chlorine + :sulphur-dioxide, :hydrogen-chloride}) -;; Use underscores `_` to mark values we don't care for. -(let [[_, _, e] planets] - (str (:pname e) - " is the third rock from the Sun.")) +(poison-gas? :oxygen) ; falsey +(poison-gas? :chlorine) ; truthy -;; Use `:as` to also alias the whole structure. -(let [[_, _, e :as planet-names] (map :pname planets)] - {:useless-trivia (str e " is the third rock from the Sun.") - :planet-names planet-names}) +;; Collections are "open", i.e. very flexible +;; - We can make collections out of anything +;; Recall: +(def a-bunch-of-values + [nil, false, ; falsey + 42, :a, "foo", true, ; truthy + {:a 1, :b 2}, [1 2 3 4], ; truthy + '(), {}, [], ""]) ; truthy -;; "Associative" De-structuring -;; -;; - Syntax to reach into associative data structures -;; (having key-value semantics), in arbitrary ways. -;; -;; - Note: Clojure "Records" and vectors are associative too -;; -;; - Follow the structure of the collection, and mechanically -;; bind values to symbols by key name. +(map boolean a-bunch-of-values) -;; Instead of looking up values like this: -(:pname earth-alt) +;; And since functions are values too: +(map (fn [f] (f 42)) + [str identity inc dec (fn [x] x)]) -;; We can follow the map's structure like this: -(let [{p :pname} earth-alt] - p) -;; Compose it with positional destructuring: -(let [[_, _, {e :pname}] planets] - (str e " is the third rock from the Sun.")) +;; We use the flexibility of collections, to model +;; real-world objects and logic as we please -;; And instead of doing lookups one at a time: -(str (:pname earth-alt) " has " - (:moons earth-alt) " moon, " - (:oxygen (:atmosphere earth-alt)) "% Oxygen, " - (:argon (:atmosphere earth-alt)) "% Argon, and " - (:traces (:atmosphere earth-alt)) "% trace gases.") +;; Predicates and operations +{:number-checks [even? pos? integer? (fn [x] (> x 42))] + :number-ops [str identity inc dec (fn [x] x)]} -;; We can arbitrarily de-structure maps, directly: -(let [{p :pname - m :moons - {traces :traces - Ar :argon - O2 :oxygen} :atmosphere} earth-alt] - (str p " has " - m " moon, " - O2 "% Oxygen, " - Ar "% Argon, and " - traces "% trace gases.")) +;; A data table: +[[:name :age :country] + ["Foo" 10 "India"] + ["Bar" 21 "Australia"] + ["Baz" 18 "Turkey"] + ["Qux" 42 "Chile"]] -;; The `:keys` form lets us de-structure more concisely: -;; - Note: in this style, we must exactly match spellings of -;; symbol names, with spellings of the keys we wish to bind. -(let [{:keys [pname moons] - {:keys [oxygen argon traces]} :atmosphere} - earth-alt] - (str pname " has " - moons " moon, " - oxygen "% Oxygen, " - argon "% Argon, and " - traces "% trace gases.")) +;; HTML (ref: Hiccup templates) +[:div {:class "wow-list"} + [:ul (map (fn [x] [:li x]) + [1 2 3 4])]] - -;; More powerfully, the `:keys` form lets us: -;; - extract multiple values, -;; - define fallbacks for missing values, _and_ -;; - alias the original input. -;; All in one shot: - -(defn summarise-planet - [{:keys [pname moons] - {:keys [oxygen argon traces] - :or {oxygen "unknown" - argon "unknown" - traces "unknown"}} :atmosphere - :as planet}] - {:summary (str pname " has " - moons " moon(s), " - oxygen " % O2, " - argon " % Argon, and " - traces" % of trace gases.") - :planet planet}) - -(summarise-planet earth-alt) - -(summarise-planet {:pname "Mercury", :moons 0, :mass 0.0533}) - -(map summarise-planet - planets) + +;; Musical patterns (ref: github.com/ssrihari/ragavardhini) +{:arohanam [:s :r3 :g3 :m1 :p :d1 :n2 :s.], + :avarohanam [:s. :n2 :d1 :p :m1 :g3 :r3 :s]} + + +;; DB queries (ref: Datomic) +(comment + [:find ?name ?duration + :where [?e :artist/name "The Beatles"] + [?track :track/artists ?e] + [?track :track/name ?name] + [?track :track/duration ?duration]]) + + +;; Starfleet mission configurations +{:inhabit {:starships 5, :battle-cruisers 5, + :orbiters 5, :cargo-ships 5, + :probes 30} + :colonise {:starships 1, :probes 50} + :probe {:orbiters 1, :probes 100} + :observe {:orbiters 1, :probes 10}} -;; Self-documenting function API: -;; -;; Last, but not least, de-structuring function arguments -;; automatically gives us self-documenting function signatures. - -#_(clojure.repl/doc summarise-planet) ; Evaluate this in the REPL - -#_(meta #'summarise-planet) - -;; RECAP: -;; -;; - hash-maps let us conveniently represent objects we wish to -;; model and query -;; - We can query hash-maps variously with keywords, `get`, and `get-in` -;; - If we use keywords as keys in hash-maps, querying is dead-simple -;; - We can define our own functions with `defn`, using this syntax: -;; -;; (defn function-name -;; [arg1 arg2 arg3 ... argN] -;; (body of the function)) -;; -;; - Using general-purpose data structures, and writing general-purpose -;; functions lets us do more with less +;; Only limited by your imagination! From a6ac84d14125873f4554978fda15a3b19c091561 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 29 Oct 2018 20:34:22 +0530 Subject: [PATCH 07/62] Simplify ex03, avoid destructuring & fancy HoFs --- .../ex03_data_and_functions.clj | 273 +++++++++--------- 1 file changed, 133 insertions(+), 140 deletions(-) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index 82cec06..98f618b 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -16,14 +16,40 @@ p/target-planets (map :pname p/target-planets) -;; Now, let's colonize planets! ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Some constants and utilities +;; Let's colonize planets! ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def starfleet-mission-configurations + "Associate mission directives and mission configurations of Starfleet vessels. + + The Office of Interstellar Affairs (OIA) issues mission directives + based on its analysis of planets." + + {:inhabit {:starships 5, :battle-cruisers 5, + :orbiters 5, :cargo-ships 5, + :probes 30} + + :colonise {:starships 1, :probes 50} + + :probe {:orbiters 1, :probes 100} + + :observe {:orbiters 1, :probes 10}}) + + + +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Basic Planetary Analysis +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +;; Some basic constants, utility functions, and "predicate" +;; functions to test a given planet for particular conditions. + + (def tolerances "Define low/high bounds of planetary characteristics we care about." {:co2 {:low 0.1, :high 5.0} @@ -31,6 +57,11 @@ p/target-planets :surface-temp-deg-c {:low -125, :high 60}}) +(def poison-gas? + "A set of poison gases." + #{:chlorine, :sulphur-dioxide, :carbon-monoxide}) + + (defn lower-bound [tolerance-key] (get-in tolerances [tolerance-key :low])) @@ -41,37 +72,32 @@ p/target-planets (get-in tolerances [tolerance-key :high])) -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Functions to test a planet for particular conditions -;; - We make them return truthy/falsey values -;; - We call such functions "predicates" -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (defn atmosphere-present? - [{:keys [atmosphere] :as planet}] - (not-empty atmosphere)) + [planet] + (not-empty (:atmosphere planet))) #_(map :pname (filter atmosphere-present? p/target-planets)) (defn co2-tolerable? - [{{:keys [carbon-dioxide] - :or {carbon-dioxide 0.0}} :atmosphere - :as planet}] - (<= (lower-bound :co2) - carbon-dioxide - (upper-bound :co2))) + [planet] + (let [co2 (get-in planet + [:atmosphere :carbon-dioxide])] + (when co2 + (<= (lower-bound :co2) + co2 + (upper-bound :co2))))) #_(map :pname (filter co2-tolerable? p/target-planets)) (defn gravity-tolerable? - [{:keys [gravity] :as planet}] - (when gravity + [planet] + (when (:gravity planet) (<= (lower-bound :gravity) - gravity + (:gravity planet) (upper-bound :gravity)))) #_(map :pname @@ -79,88 +105,49 @@ p/target-planets (defn surface-temp-tolerable? - [{{:keys [low high]} :surface-temp-deg-c - :as planet}] - (when (and low high) - (<= (lower-bound :surface-temp-deg-c) - low - high - (upper-bound :surface-temp-deg-c)))) + [planet] + (let [temp (:surface-temp-deg-c planet) + low (:low temp) + high (:high temp)] + (when (and low high) + (<= (lower-bound :surface-temp-deg-c) + low + high + (upper-bound :surface-temp-deg-c))))) #_(map :pname (filter surface-temp-tolerable? p/target-planets)) -(def poison-gas? - "A set of poison gases." - #{:chlorine, :sulphur-dioxide, :carbon-monoxide}) - - - -;; We can use sets as predicates. Sets behave as functions -;; that test for set membership. - -(poison-gas? :oxygen) ; falsey - -(poison-gas? :chlorine) ; truthy - - (defn air-too-poisonus? - [{:keys [atmosphere] :as planet}] - (some (fn [[gas-name gas-pct]] - (and (poison-gas? gas-name) - (<= 1.0 gas-pct))) - atmosphere)) + "The atmosphere is too poisonous, if the concentration of + any known poison gas exceeds 1.0% of atmospheric composition." + [planet] + (let [gas-too-poisonous? (fn [gas-key-pct-pair] + (and (poison-gas? (gas-key-pct-pair 0)) + (>= (gas-key-pct-pair 1) 1.0)))] + (not-empty + (filter gas-too-poisonous? + (:atmosphere planet))))) + (map :pname (filter air-too-poisonus? p/target-planets)) -;; a hash-map is a collection of key-value pairs +;; Note: a hash-map is a collection of key-value pairs (map identity {:nitrogen 78.08, :oxygen 20.95, :carbon-dioxide 0.4, :water-vapour 0.1, :argon 0.33, :traces 0.14}) -(map (fn [[k v]] (str v "% " k)) +(map (fn [pair] + (str (get pair 0) " % = " (get pair 1))) {:nitrogen 78.08, :oxygen 20.95, :carbon-dioxide 0.4, :water-vapour 0.1, :argon 0.33, :traces 0.14}) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Composite checks to -;; - test whether a given planet meets a variety of conditions. -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -(defn planetary-conditions-meet-constraint? - "Return the given planet as-is, when it meets the condition fns, - as per the constraint fn (like every?, some, not-every). - - The constraint fn must return a Boolean true/false value." - [constraint] - (fn [conditions planet] - (when (true? (constraint (fn [f] (f planet)) - conditions)) - planet))) - - -(def planet-meets-any-one-condition? - (planetary-conditions-meet-constraint? - some)) - - -(def planet-meets-all-conditions? - (planetary-conditions-meet-constraint? - every?)) - - -(def planet-meets-no-condition? - (planetary-conditions-meet-constraint? - not-any?)) - - -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Conditions for colonisation +;; Composite checks to perform on a given planet ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -179,16 +166,48 @@ p/target-planets air-too-poisonus?]) +(defn conditions-met + "Return only those condition fns that a planet meets. + An empty collection means no conditions were met." + [condition-fns planet] + (filter (fn [condition-fn] + (condition-fn planet)) + condition-fns)) + + +(defn planet-meets-no-condition? + [conditions planet] + ((comp zero? count conditions-met) + conditions planet)) + + +(def planet-meets-any-one-condition? + (complement planet-meets-no-condition?)) + + +(defn planet-meets-all-conditions? + [conditions planet] + (= (count conditions) + (count (conditions-met conditions planet)))) + + +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Composite checks to +;; - test whether a given planet meets a variety of conditions. +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (defn habitable? "We deem a planet habitable, if it has all minimally good conditions, and no fatal conditions." [planet] - (planet-meets-no-condition? - fatal-conditions - (planet-meets-all-conditions? - minimal-good-conditions - planet))) - + (when (and (planet-meets-no-condition? + fatal-conditions + planet) + (planet-meets-all-conditions? + minimal-good-conditions + planet)) + planet)) #_(map :pname (filter habitable? p/target-planets)) @@ -198,12 +217,13 @@ p/target-planets "We deem a planet colonisable, if it has at least one minimally good condition, and no fatal conditions." [planet] - (and (planet-meets-any-one-condition? - minimal-good-conditions - planet) - (planet-meets-no-condition? - fatal-conditions - planet))) + (when (and (planet-meets-any-one-condition? + minimal-good-conditions + planet) + (planet-meets-no-condition? + fatal-conditions + planet)) + planet)) #_(map :pname (filter colonisable? p/target-planets)) @@ -212,12 +232,13 @@ p/target-planets (defn observe-only? "We select a planet for orbital observation, if it only has harsh surface conditions." [planet] - (and (planet-meets-any-one-condition? - fatal-conditions - planet) - (planet-meets-no-condition? - minimal-good-conditions - planet))) + (when (and (planet-meets-any-one-condition? + fatal-conditions + planet) + (planet-meets-no-condition? + minimal-good-conditions + planet)) + planet)) #_(map :pname (filter observe-only? p/target-planets)) @@ -225,54 +246,26 @@ p/target-planets ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Enrich planetary data with analytical results +;; Enrich planetary data with Starfleet mission information +;; from the Office of Interstellar Affairs. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn analyze-planet +(defn issue-mission-directive [planet] (cond (habitable? planet) :inhabit (colonisable? planet) :colonise - (observe-only? planet) :observe-only - :else :send-probes)) - - -#_(map (juxt :pname analyze-planet) - p/target-planets) - -(map analyze-planet p/target-planets) - - -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Starfleet assigns mission vessels... -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -(defmulti assign-vessels ; multi-method definition - analyze-planet) ; dispatch function - - -(defmethod assign-vessels :inhabit [planet] - (assoc planet - :vessels {:starships 5, :orbiters 5, :probes 100, - :battle-cruisers 5, :cargo-ships 5})) - + (observe-only? planet) :observe + :else :probe)) -(defmethod assign-vessels :colonise [planet] - (assoc planet - :vessels {:starships 1, :probes 50})) - -(defmethod assign-vessels :send-probes [planet] - (assoc planet - :vessels {:orbiters 1, :probes 10})) - - -(defmethod assign-vessels :observe-only [planet] - (assoc planet - :vessels {:orbiters 1})) +(defn assign-vessels + [planet] + (let [mission-directive (issue-mission-directive planet)] + (assoc planet + :mission-directive mission-directive + :mission-vessels (mission-directive starfleet-mission-configurations)))) -#_(map (juxt :pname analyze-planet :vessels) - (map assign-vessels p/target-planets)) +#_(map (juxt :pname assign-vessels) + p/target-planets) From c82b34e3d31e7564cbb0c4d67ef6928ad05e76e5 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 29 Oct 2018 20:36:22 +0530 Subject: [PATCH 08/62] Make ex04 about function API design, destructuring --- src/clojure_by_example/ex04_api_design.clj | 158 ++++ src/clojure_by_example/ex04_control_flow.clj | 750 ------------------- 2 files changed, 158 insertions(+), 750 deletions(-) create mode 100644 src/clojure_by_example/ex04_api_design.clj delete mode 100644 src/clojure_by_example/ex04_control_flow.clj diff --git a/src/clojure_by_example/ex04_api_design.clj b/src/clojure_by_example/ex04_api_design.clj new file mode 100644 index 0000000..1bf8e19 --- /dev/null +++ b/src/clojure_by_example/ex04_api_design.clj @@ -0,0 +1,158 @@ +(ns clojure-by-example.ex04-api-design) + +;; EX04: Lesson Goals: +;; - See how to "de-structure" data (it's a powerful, flexible lookup mechanism) +;; - Leverage de-structuring to design a self-documenting function API + + +;; "De-structuring" + +;; - Clojure lets us reach into data structures in arbitrary ways, +;; and extract multiple values in one go +;; +;; - We use this for clean lookups in `let` bindings, and +;; in function signatures, to design expressive APIs. + + +;; Positional De-structuring +;; +;; - Pull apart sequential, ordered data structures like +;; lists, vectors, and any other sequence with linear access +;; +;; - Follow the structure of the collection, and mechanically +;; bind values to symbols by position. + + +;; Instead of looking up values by index position: +(str "The first two planets: " + (:pname (get planets 0)) " and " + (:pname (get planets 1)) ".") + + +;; We can bind symbols by position (match structure to structure): +(let [[m, v] planets] + (str "The first two planets: " + (:pname m) " and " + (:pname v) ".")) + + +;; Use underscores `_` to mark values we don't care for. +(let [[_, _, e] planets] + (str (:pname e) + " is the third rock from the Sun.")) + + +;; Use `:as` to also alias the whole structure. +(let [[_, _, e :as planet-names] (map :pname planets)] + {:useless-trivia (str e " is the third rock from the Sun.") + :planet-names planet-names}) + + +;; "Associative" De-structuring +;; +;; - Syntax to reach into associative data structures +;; (having key-value semantics), in arbitrary ways. +;; +;; - Note: Clojure "Records" and vectors are associative too +;; +;; - Follow the structure of the collection, and mechanically +;; bind values to symbols by key name. + + +;; Instead of looking up values like this: +(:pname earth-alt) + +;; We can follow the map's structure like this: +(let [{p :pname} earth-alt] + p) + +;; Compose it with positional destructuring: +(let [[_, _, {e :pname}] planets] + (str e " is the third rock from the Sun.")) + + +;; And instead of doing lookups one at a time: +(str (:pname earth-alt) " has " + (:moons earth-alt) " moon, " + (:oxygen (:atmosphere earth-alt)) "% Oxygen, " + (:argon (:atmosphere earth-alt)) "% Argon, and " + (:traces (:atmosphere earth-alt)) "% trace gases.") + + +;; We can arbitrarily de-structure maps, directly: +(let [{p :pname + m :moons + {traces :traces + Ar :argon + O2 :oxygen} :atmosphere} earth-alt] + (str p " has " + m " moon, " + O2 "% Oxygen, " + Ar "% Argon, and " + traces "% trace gases.")) + + +;; The `:keys` form lets us de-structure more concisely: +;; - Note: in this style, we must exactly match spellings of +;; symbol names, with spellings of the keys we wish to bind. +(let [{:keys [pname moons] + {:keys [oxygen argon traces]} :atmosphere} + earth-alt] + (str pname " has " + moons " moon, " + oxygen "% Oxygen, " + argon "% Argon, and " + traces "% trace gases.")) + + +;; More powerfully, the `:keys` form lets us: +;; - extract multiple values, +;; - define fallbacks for missing values, _and_ +;; - alias the original input. +;; All in one shot: + +(defn summarise-planet + [{:keys [pname moons] + {:keys [oxygen argon traces] + :or {oxygen "unknown" + argon "unknown" + traces "unknown"}} :atmosphere + :as planet}] + {:summary (str pname " has " + moons " moon(s), " + oxygen " % O2, " + argon " % Argon, and " + traces" % of trace gases.") + :planet planet}) + +(summarise-planet earth-alt) + +(summarise-planet {:pname "Mercury", :moons 0, :mass 0.0533}) + +(map summarise-planet + planets) + + +;; Self-documenting function API: +;; +;; Last, but not least, de-structuring function arguments +;; automatically gives us self-documenting function signatures. + +#_(clojure.repl/doc summarise-planet) ; Evaluate this in the REPL + +#_(meta #'summarise-planet) + +;; RECAP: +;; +;; - hash-maps let us conveniently represent objects we wish to +;; model and query +;; - We can query hash-maps variously with keywords, `get`, and `get-in` +;; - If we use keywords as keys in hash-maps, querying is dead-simple +;; - We can define our own functions with `defn`, using this syntax: +;; +;; (defn function-name +;; [arg1 arg2 arg3 ... argN] +;; (body of the function)) +;; +;; - Using general-purpose data structures, and writing general-purpose +;; functions lets us do more with less diff --git a/src/clojure_by_example/ex04_control_flow.clj b/src/clojure_by_example/ex04_control_flow.clj deleted file mode 100644 index 4af2961..0000000 --- a/src/clojure_by_example/ex04_control_flow.clj +++ /dev/null @@ -1,750 +0,0 @@ -(ns clojure-by-example.ex04-control-flow) - - -;; Ex04: LESSON GOALS -;; -;; - Introduce different ways to do data-processing logic in Clojure -;; - with branching control structures (if, when, case, cond) -;; - without branching structures (we have already sneakily done this) -;; - predicates and boolean expressions -;; -;; - Have some more fun with much more sophisticated planets, -;; using control structures, and the stuff we learned so far - - -;; The logical base for logic: - -;; Boolean - -(true? true) ; `true` is boolean true - -(true? false) ; `false` is boolean false - - -;; Falsey - -;; `nil` is the only non-boolean "falsey" value - - -;; Truthy -;; - basically any non-nil value is truthy - -42 ; truthy -:a ; truthy -"foo" ; truthy -[7 3] ; truthy -[] ; turthy -"" ; truthy - - -;; Truthy/Falsey are NOT Boolean - -(true? 42) ; Is Truthy 42 a boolean true? - -(false? nil) ; Is Falsey nil a boolean false? - - -;; Truthy/Falsey can be cast to boolean true/false - -(boolean nil) ; coerce nil to `false` - -(map boolean - [42 :a "foo" [1 2 3 4] [] ""]) ; coerce non-nils to `true` - - -;; However, we need not coerce truthy/falsey to booleans to do -;; branching logic, as Clojure control structures understand -;; truthy and falsey values too: - -;; false is, well, false - -(if false ; if condition - :hello ; "then" expression - :bye-bye) ; "else" expression - - -;; `nil` is falsey - -(if nil - :hello - :bye-bye) - - -;; true is true, and every non-nil thing is truthy - -(if true - :hello - :bye-bye) - - -(if "Oi" - :hello - :bye-bye) - - -(if 42 - :hello - :bye-bye) - - -(if [1 2] - :hello - :bye-bye) - - - -;; `when` piggy-backs on the falsy-ness of `nil` -;; - when a condition is true, it evaluates the body and -;; returns its value -;; - otherwise, it does nothing, and returns `nil`, i.e. _falsey_ -;; - you may think of `when` as half an `if`, that always returns -;; `nil` (falsey) when the test expression is false or falsey. - -(when 42 - :hello) - - -(when false - :bye-bye) - - -(when nil - :bye-bye) - - -(when (nil? nil) - :bye-bye) - - -;; MENTAL EXERCISES -;; -;; Mental exercises to develop your intuition for how we use -;; "proper" booleans as well as truthy/falsey-ness. - - -;; EXERCISE: -;; -;; Predict what will happen... - -(map (fn [x] (if x - :hi - :bye)) - [1 2 nil 4 5 nil 7 8]) - - - -;; EXERCISE: -;; -;; Predict what will happen... - -(reduce (fn [acc x] (if x - (inc acc) - acc)) - 0 ; initial accumulator - [1 2 nil 4 5 nil 7 8]) - - -;; EXERCISE: -;; -;; Predict and compare the result of these two... - -(filter nil? [1 2 nil 4 5 nil 7 8]) - -(filter false? [1 2 nil 4 5 nil 7 8]) - - -;; EXERCISE: -;; -;; Predict and compare these three... - -(map identity - [1 2 nil 4 5 nil 7 8]) - -(filter identity - [1 2 nil 4 5 nil 7 8]) ; Ha! What happened here?! - -;; Compare the result of filter identity, with the following: -;; - And, reason about why the two look alike. -(filter (comp not nil?) - [1 2 nil 4 5 nil 7 8]) -;; Recall: `comp` composes functions into a pipeline - - - -;; INTERLUDE... -;; -;; The logic and ill-logic of `nil` in Clojure -;; -;; `nil` -;; -;; is Good and Evil, -;; something and nothing, -;; dead and alive. -;; -;; Love it or hate it, -;; you _will_ face `nil`. -;; Sooner than later, -;; in Clojure. -;; -;; Embrace it. -;; Guard against it. -;; But don't fear it. -;; -;; `nil` isn't the Enemy. -;; Fear is. -;; -;; Wield `nil` as -;; a double-edged sword. -;; For it cuts both ways. -;; -;; Ignore this, -;; and you will know -;; true suffering. - - -;; Good - `filter` knows `nil` is falsey - -(filter identity - [1 2 nil 4 5 nil 7 8]) - -;; Evil - `even?` cannot handle nothing... so, this fails: - -#_(filter even? - [1 2 nil 4 5 nil 7 8]) - -;; So... Guard functions like `even?` against the evil of nil - -(filter (fn [x] (when x - (even? x))) - [1 2 nil 4 5 nil 7 8]) - -;; `fnil` is also handy, to "patch" nil input to a function - -;; We might use it as a guard for `even?` like this: -((fnil even? 1) nil) ; pass 1 to `even?`, instead of nil -((fnil even? 2) nil) ; pass 2 to `even?`, instead of nil - -;; What's happening here? -;; - `fnil` takes a function f and some "fallback" value x, and returns -;; a function that calls f, replacing a nil first argument to f with -;; the supplied "fallback" value x. - - -;; Suppose for some strange reason, we want to treat `nil` as non-even. -;; We can nil-patch `even?` as: -(filter (fnil even? 1) - [1 2 nil 4 5 nil 7 8]) - - - -;; Lesson: -;; - Keep `nil` handling in mind, when you write your own functions. - - -;; Demonstration: -;; -;; - It's possible to use `nil` for good, and make life easier. -;; -;; - How might someone use `nil` to advantage? - -(def planets [{:pname "Venus" :moons 0} - {:pname "Mars" :moons 2} - {:pname "Jupiter" :moons 69}]) - - -;; Using `when` ... -;; -;; We might design a function that sends rockets to all moons of -;; a planet only when the planet has moons. (Instead of actually -;; sending rockets, let's just return a hash-map to express this -;; behaviour.) - - -(defn send-rockets-1 - [planet] - ;; Recall: we can "let-bind" local variables - (let [num-moons (:moons planet)] - (when (> num-moons 0) - {:send-rockets num-moons - :to-moons-of (:pname planet)}))) - -(send-rockets-1 {:pname "Venus" :moons 0}) -(send-rockets-1 {:pname "Mars" :moons 2}) - -(map send-rockets-1 planets) ; see, nil rockets for Venus - - -;; Later, someone may ask us what we did! And, we might design -;; a function to answer their question: -(defn good-heavens-what-did-you-do? - [rocket-info] - (if rocket-info ; we will treat rocket-info as truthy/falsey - ;; do/return this if true... - (format "I sent %d rockets to the moons of %s! Traa la laaa..." - (:send-rockets rocket-info) - (:to-moons-of rocket-info)) - ;; do/return this if false... - "No rockets sent. Waaah!")) - - -;; And we will answer... -(map good-heavens-what-did-you-do? - (map send-rockets-1 planets)) - - - -;; But suppose, using `if` ... -;; - we re-design the function like this: - -(defn send-rockets-2 [planet] - (let [num-moons (:moons planet)] - (if (> num-moons 0) - {:send-rockets num-moons - :to-moons-of (:pname planet)} - "Do nothing."))) - -(send-rockets-2 {:pname "Venus" :moons 0}) -(send-rockets-2 {:pname "Mars" :moons 2}) - -(map send-rockets-2 planets) ; see, truthy output (string) for Venus - -;; Now, suppose we use `send-rockets-2`, and later somebody asks us -;; what we did. -;; - How do we design a function to produce the same result as we got -;; earlier? -;; -#_(defn good-heavens-what-did-you-do-again??? - [rocket-info] - ;; Fix to ensure the same output as we produced earlier. - (if 'FIX - 'FIX - 'FIX)) - - -;; We should be able to provide the same answers as before... - -#_(map good-heavens-what-did-you-do-again??? - (map send-rockets-2 planets)) - - -;; Lesson: -;; - Using `nil` this way can reduce the need for writing explicit -;; `if` conditionals. This give us more generality, because now -;; we don't have to keep thinking of specialized literal values. - - -;; `case` and `cond` -;; - are also available to do branching logic: - -(map (fn [num-moons] - ;; Use `cond` when you have to decide what to do based on - ;; testing the value of a thing. - (cond - (nil? num-moons) "Do nothing!" - (zero? num-moons) "Send zero rockets." - (= num-moons 1) "Send a rocket." - :else (str "Send " num-moons " rockets!"))) - - [nil 0 1 42]) - - -(map (fn [num-moons] - ;; Use case when you can decide what to do based on the - ;; actual value of a thing. - (case num-moons - nil "Do nothing!" - 0 "Send zero rockets." - 1 "Send a rocket." - (str "Send " num-moons " rockets!"))) ; default expression - - [nil 0 1 42]) - - -;; EXERCISE: -;; -;; Try to reason from first principles: -;; -;; Why does cond require `:else` to mark the last / default condition, -;; but case simply treats the last expression as default? -;; -;; (Hint: is `:else` an expression or a value?) - - - - -;; And now for something completely different. - - - -;; Clojure hash-sets -;; - can be used as predicates (and often are used this way) - -(= #{:a :b :c} ; A hash-set of three things, :a, :b, and :c. - (hash-set :a :b :b :c :a :c :c :c)) - - -;; A hash-set behaves like a function to test for set membership. - -(#{:a :b :c} :a) ; Does the set contain :a? Truthy. - -(#{:a :b :c} :z) ; Does the set contain :z? Falsey. - - -;; How do Clojure programmers use sets as predicates? - -(def colonize-it? #{"Earth" "Mars"}) - - -((comp colonize-it? :pname) {:pname "Earth"}) - - -((comp colonize-it? :pname) {:pname "Venus"}) - - -(filter (comp colonize-it? :pname) - [{:pname "Mercury"} - {:pname "Venus"} - {:pname "Earth"} - {:pname "Mars"} - {:pname "Jupiter"}]) - - - -;; Lesson-end exercise - - -;; LET'S COLONIZE PLANETS!!!! -;; \\//_ - - -(def target-planets - [{:pname "Mercury" - :mass 0.055 - :radius 0.383 - :moons 0 - :atmosphere {}} ; empty hash map means no atmosphere - - {:pname "Venus" - :mass 0.815 - :radius 0.949 - :moons 0 - :atmosphere {:carbon-dioxide 96.45 :nitrogen 3.45 - :sulphur-dioxide 0.015 :traces 0.095}} - - {:pname "Earth" - :mass 1 - :radius 1 - :moons 1 - :atmosphere {:nitrogen 78.08 :oxygen 20.95 :carbon-dioxide 0.4 - :water-vapour 0.10 :argon 0.33 :traces 0.14}} - - {:pname "Mars" - :mass 0.107 - :radius 0.532 - :moons 2 - :atmosphere {:carbon-dioxide 95.97 :argon 1.93 :nitrogen 1.89 - :oxygen 0.146 :carbon-monoxide 0.056 :traces 0.008}} - - {:pname "Chlorine Planet" - :mass 2.5 - :radius 1.3 - :moons 4 - :atmosphere {:chlorine 100.0}} - - {:pname "Insane Planet" - :mass 4.2 - :radius 1.42 - :moons 42 - :atmosphere {:sulphur-dioxide 80.0 :carbon-monoxide 10.0 - :chlorine 5.0 :nitrogen 5.0}}]) - - -;; EXERCISE: -;; -;; Define a set of `poison-gases` -;; - Let's say :chlorine, :sulphur-dioxide, :carbon-monoxide are poisons - -(def poison-gases 'FIX) - -;; Is the gas poisonous? -#_(poison-gases :oxygen) -#_(poison-gases :chlorine) - - -;; EXERCISE: -;; -;; Write a "predicate" function to check if a given planet is "Earth". -;; - -(defn earth? - [planet] - 'FIX) - - -;; EXERCISE: -;; -;; Write a predicate function to check if a planet has -;; at least 0.1% :carbon-dioxide in its atmosphere. - -(defn carbon-dioxide? - [planet] - 'FIX) - - -#_(map :pname - (filter carbon-dioxide? target-planets)) - -;; EXERCISE: -;; -;; Having no atmosphere is a bad thing, you know. -;; -;; Write a "predicate" function that returns truthy -;; if a planet has no atmosphere. It should return falsey -;; if the planet _has_ an atmosphere. -;; -;; Call it `no-atmosphere?` -;; -;; Use `empty?` to check if the value of :atmosphere is empty. -(empty? {}) ; is true. It's an empty hash-map. -;; -;; Type your solution below - - - -;; Quick-n-dirty test -#_(filter no-atmosphere? target-planets) - - - -;; EXERCISE: -;; -;; Let's say the air is too poisonous if the atmosphere contains -;; over 1.0 percent of _any_ poison gas. -;; -;; Write a "predicate" function that checks this, given a planet. -;; -;; Call it `air-too-poisonous?`. -;; -;; Use the following five ideas: -;; -;; [1] The `poison-gases` set we defined previously can be used -;; as a truthy/falsey predicate. Check: -(poison-gases :oxygen) -(poison-gases :chlorine) -;; -;; [2] A hash-map is a _collection_ of key-value _pairs_ / "kv" tuples -(map identity {:a 1 :b 2 :c 3}) ; see?! -;; -;; [3] Given a `kv` pair from a hash-map, (first kv) will always be -;; the key part, and (second kv) will always be the value part. -(map first {:a 1 :b 2 :c 3}) -(map second {:a 1 :b 2 :c 3}) -;; -;; [4] So, we can do something like this: -(filter (fn [kv] - (when (#{:a :b :c} - (first kv)) - (even? (second kv)))) - {:a 1 :b 2 :c 4 :d 8 :e 10 :f 12}) -;; -;; [5] Finally, recall that we can test for empty? collections: -(empty? []) -(not (empty? [])) -;; -;; -;; Now, combine ideas [1] to [5] to fix the function below: - - -(defn air-too-poisonous? - [planet] - (let [atmosphere 'FIX] - ;; Re-purpose, and fix the logic above to do the needful. - 'FIX)) - - -;; Quick-n-dirty test -#_(map :pname - (filter air-too-poisonous? target-planets)) - - - -;; EXERCISE: -;; -;; Understand the next few functions. - - -(defn planet-has-some-good-conds? - "Given a collection of functions that check a planet for - 'good conditions', return true if a given planet satisfies at least - one 'good condition'." - [good-condition-fns planet] - ;;`some` takes a predicate and a collection, and returns true - ;; as soon as it finds an item that returns true for the predicate. - (some (fn [good?] (good? planet)) - good-condition-fns)) - -;; Quick-n-dirty test: -;; - Let's say it's good to be Earth (yay), and -;; - It's good to have carbon dioxide in the atmosphere. - -#_(filter (fn [planet] - (planet-has-some-good-conds? - [earth? carbon-dioxide?] - planet)) - target-planets) - -;; OR we could use `partial`: - -#_(filter (partial planet-has-some-good-conds? - [earth? carbon-dioxide?]) - target-planets) - -;; What does `partial` do? -;; - Retrieve documentation for `partial` using `clojure.repl/doc`. -;; (More on "REPL" utilities later.) - -(clojure.repl/doc partial) ; evaluate and check the console/repl window - -;; Then fix these to make them work: - -#_((partial (fn [a b c] (+ a b c)) - 1) - 'FIX 'FIX) - -#_((partial (fn [a b c] (+ a b c)) - 1 2) - 'FIX) - - -;; EXERCISE: -;; -;; Given a collection of bad conditions, and a planet, return true if -;; the planet has _no_ bad conditions. - -(defn planet-has-no-bad-conds? - [bad-condition-fns planet] - 'FIX) - -;; Quick-n-dirty test: -#_(filter (partial planet-has-no-bad-conds? - [air-too-poisonous? no-atmosphere?]) - target-planets) - -#_(filter (complement ; Aha! Remember `complement`? - (partial planet-has-no-bad-conds? - [air-too-poisonous? no-atmosphere?])) - target-planets) - - - -;; EXERCISE: -;; -;; Let's say that a habitable planet has some good conditions, -;; and NO bad conditions. -;; -;; Define a function that checks true/truthy for this, given -;; good-condition-fns, bad-condition-fns, and a planet. -;; -;; Re-use: -;; - `planet-has-some-good-conds?`, and -;; - `planet-has-no-bad-conds?` - -(defn habitable-planet? - [good-condition-fns FIX1 FIX2] ; <- Fix args - 'FIX) - - -;; EXERCISE: -;; -;; And finally, re-use anything you can from the discussion so far, -;; to write a function that groups a given collection of planets into -;; :habitable, and :inhospitable. -;; -;; The function must internally know what functions will check for -;; good conditions, and what functions check for bad conditions. -;; -;; - Assume it's good to be earth, OR to have carbon-dioxide in the air. -;; - Assume it's bad to have no atmosphere, or to have poison gases. -;; -;; Hint: here's your chance to make good use of `let`, `partial`, -;; and `complement`. And to cleanly return the grouping as a hash-map. - -(defn group-by-habitable - [FIX] - 'FIX) - - -;; Quick-n-dirty test: -(defn colonize-habitable-planets! - [planets] - (let [send-rockets! (fn [p] - (str "Send rockets to " (:pname p) " now!"))] - ((comp (partial map send-rockets!) - :habitable - group-by-habitable) - planets))) - - -#_(colonize-habitable-planets! target-planets) - - - -;; RECAP: -;; -;; Phew! That was a _lot_ of computing. -;; -;; But we did it, with a rather small set of core things and core ideas: -;; -;; Core Clojure things: -;; -;; - named functions -;; - anonymous functions -;; - `let`-bound locals -;; - Sequences: -;; - hash-maps (plus keywords + keyword access) -;; - vectors (plus first and second to get the 1st and 2nd item) -;; - hash-sets (plus their great utility as predicates) -;; - Sequence functions: -;; - map -;; - filter -;; - reduce -;; - some -;; - empty? -;; - Branching and boolean logic: -;; - true/false and truthy/falsey -;; - nil handling -;; - if -;; - when -;; - case -;; - cond -;; - not -;; - "Higher Order" Functions (HOFs), for convenience. HOFs are -;; functions that either take functions as arguments, or -;; return functions as results, or do both, i.e. take functions -;; as arguments _and_ return functions as results. -;; - We introduced these HOFs: -;; - comp -;; - complement -;; - partial -;; - fnil -;; - And you'll see how identity, map, reduce, filter are HOFs too! -;; -;; Core Clojure ideas: -;; -;; - Functions are values. That's why we can: -;; - pass them as arguments, -;; - return them as results of functions, and -;; - even make collections of them (like our "good conditions" and -;; "bad conditions".) -;; -;; - Good functions "compose": -;; We write many small functions that each do one simple task well. -;; And then, we combine and mix-and-match those functions to do -;; increasingly sophisticated tasks. -;; -;; - We tend to think in terms of sequences, and sequence operations. -;; (As opposed to looping operations on items of sequences.) -;; -;; - We strongly prefer to model real-world objects as pure data, -;; then and use many small functions to progressively transform our -;; data models into real-world outcomes. From 09e57802b2e1bbb756ec338b039f723566852ec7 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 1 Dec 2018 20:40:11 +0530 Subject: [PATCH 09/62] Rename utils to fun, because it is so... --- .../{utils/core.clj => fun/inspect_nasa_planets.clj} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/clojure_by_example/{utils/core.clj => fun/inspect_nasa_planets.clj} (97%) diff --git a/src/clojure_by_example/utils/core.clj b/src/clojure_by_example/fun/inspect_nasa_planets.clj similarity index 97% rename from src/clojure_by_example/utils/core.clj rename to src/clojure_by_example/fun/inspect_nasa_planets.clj index 54a48a3..ac5dc56 100644 --- a/src/clojure_by_example/utils/core.clj +++ b/src/clojure_by_example/fun/inspect_nasa_planets.clj @@ -1,4 +1,4 @@ -(ns clojure-by-example.utils.core +(ns clojure-by-example.fun.inspect-nasa-planets (:require [net.cgrand.enlive-html :as html] [clojure.string :as cs] [clojure.inspector :as inspect])) @@ -131,7 +131,7 @@ (map :content) flatten ((partial map - (fn [s] (cs/replace s #" " "")))) + (fn [s] (cs/replace s #"�" "")))) rest ; get rid of empty column's label (map keywordize)) massage-row-label #(cond (#{:a} (:tag %)) @@ -148,7 +148,7 @@ (partial map massage-row-label) :content))) cleanup-stats (comp (partial partition num-cols) - (partial filter #(not= " " %)) + (partial filter #(not= "�" %)) flatten #(map :content %) #(take num-stats %) From 93bba3fa32b201044769df83e5c41378af19b8e9 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 1 Dec 2018 20:44:28 +0530 Subject: [PATCH 10/62] Fix typo in fn name (thanks Mayank Pahwa) --- src/clojure_by_example/ex03_data_and_functions.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index 98f618b..bcf06f8 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -119,7 +119,7 @@ p/target-planets (filter surface-temp-tolerable? p/target-planets)) -(defn air-too-poisonus? +(defn air-too-poisonous? "The atmosphere is too poisonous, if the concentration of any known poison gas exceeds 1.0% of atmospheric composition." [planet] @@ -132,7 +132,7 @@ p/target-planets (map :pname - (filter air-too-poisonus? p/target-planets)) + (filter air-too-poisonous? p/target-planets)) ;; Note: a hash-map is a collection of key-value pairs (map identity @@ -163,7 +163,7 @@ p/target-planets "A collection of functions that tell us about the fatality of planetary conditions." [(complement atmosphere-present?) - air-too-poisonus?]) + air-too-poisonous?]) (defn conditions-met From b881d3446299cb82474006c30003e614c51ab079 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 1 Dec 2018 20:45:23 +0530 Subject: [PATCH 11/62] Tweak some copy and comment forms in ex02 --- src/clojure_by_example/ex02_domain_as_data.clj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/clojure_by_example/ex02_domain_as_data.clj b/src/clojure_by_example/ex02_domain_as_data.clj index c74bafd..948c7eb 100644 --- a/src/clojure_by_example/ex02_domain_as_data.clj +++ b/src/clojure_by_example/ex02_domain_as_data.clj @@ -142,7 +142,7 @@ ;; Collections are "open", i.e. very flexible -;; - We can make collections out of anything +;; - We can make collections out of almost anything ;; Recall: (def a-bunch-of-values @@ -154,7 +154,8 @@ (map boolean a-bunch-of-values) -;; And since functions are values too: +;; And since functions are values too, we can potentially use +;; collections of functions like this: (map (fn [f] (f 42)) [str identity inc dec (fn [x] x)]) @@ -188,12 +189,11 @@ ;; DB queries (ref: Datomic) -(comment - [:find ?name ?duration +#_[:find ?name ?duration :where [?e :artist/name "The Beatles"] [?track :track/artists ?e] [?track :track/name ?name] - [?track :track/duration ?duration]]) + [?track :track/duration ?duration]] ;; Starfleet mission configurations From ebe8a2eb216ae94502abf856eb63ec530d43778d Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 1 Dec 2018 20:47:07 +0530 Subject: [PATCH 12/62] Don't use not-empty in ex03, explore stdlib later --- src/clojure_by_example/ex03_data_and_functions.clj | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index bcf06f8..fd02672 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -74,7 +74,7 @@ p/target-planets (defn atmosphere-present? [planet] - (not-empty (:atmosphere planet))) + (not (empty? (:atmosphere planet)))) #_(map :pname (filter atmosphere-present? p/target-planets)) @@ -126,9 +126,10 @@ p/target-planets (let [gas-too-poisonous? (fn [gas-key-pct-pair] (and (poison-gas? (gas-key-pct-pair 0)) (>= (gas-key-pct-pair 1) 1.0)))] - (not-empty - (filter gas-too-poisonous? - (:atmosphere planet))))) + (not + (empty? + (filter gas-too-poisonous? + (:atmosphere planet)))))) (map :pname From 31537f49ab8ebc4a7d7883ebfbf40a4d1409af30 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 1 Dec 2018 21:18:29 +0530 Subject: [PATCH 13/62] Bump clj version, fix deps, drop lighttable compat --- project.clj | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/project.clj b/project.clj index 8e26e27..b1ad648 100644 --- a/project.clj +++ b/project.clj @@ -3,11 +3,8 @@ :url "https://github.com/inclojure-org/clojure-by-example" :license {:name "MIT" :url "https://opensource.org/licenses/MIT"} - :dependencies [[org.clojure/clojure "1.8.0"]] - ;; LightTable dependencies - ;; https://github.com/LightTable/Clojure/#connect-to-remote-nrepl - :profiles {:dev {:dependencies [[lein-light-nrepl "0.3.3"] + ;; Requirements: Java 8 or higher (recommended: Java 8 or Java 11) + :dependencies [[org.clojure/clojure "1.10.0-RC2"]] + :profiles {:dev {:dependencies [[org.clojure/data.json "0.2.6"] [enlive "1.1.6"] - [cheshire "5.8.0"] - [criterium "0.4.4"]]}} - :repl-options {:nrepl-middleware [lighttable.nrepl.handler/lighttable-ops]}) + [rewrite-clj "0.6.1"]]}}) From 8fb13653a9bee968aca5b00de29eee326d2cd40c Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 1 Dec 2018 21:59:26 +0530 Subject: [PATCH 14/62] Add fun way to reformat ex code for in-class use --- src/clojure_by_example/fun/workshop_fmt.clj | 102 ++++++++++++++++++++ src/clojure_by_example/workshop/.gitignore | 1 + src/clojure_by_example/workshop/README.txt | 7 ++ 3 files changed, 110 insertions(+) create mode 100644 src/clojure_by_example/fun/workshop_fmt.clj create mode 100644 src/clojure_by_example/workshop/.gitignore create mode 100644 src/clojure_by_example/workshop/README.txt diff --git a/src/clojure_by_example/fun/workshop_fmt.clj b/src/clojure_by_example/fun/workshop_fmt.clj new file mode 100644 index 0000000..ff6d423 --- /dev/null +++ b/src/clojure_by_example/fun/workshop_fmt.clj @@ -0,0 +1,102 @@ +(ns clojure-by-example.fun.workshop-fmt + (:require [clojure.edn :as edn] + [clojure.java.io :as io] + [rewrite-clj.zip :as z]) + (:import java.io.PushbackReader)) + +(def relative-src-path "src/clojure_by_example/") +(def relative-dest-path (str relative-src-path "workshop/")) +(def workshop-file-pattern (re-pattern "ex.*.clj")) + + +(defn elide-if-comment + [zloc] + (if (= (z/value zloc) 'comment) + (-> zloc z/up z/remove) + zloc)) + + +(defn elide-comment-forms + [zloc] + (if (z/end? zloc) + zloc + (recur (-> zloc elide-if-comment z/next)))) + + +(defn workshop-files! + [sourcedir file-pattern] + (->> (io/file sourcedir) + .listFiles + (filter #(not (.isDirectory %))) + (map #(.getName %)) + (filterv (fnil #(re-find file-pattern %) "")))) + + +(defn spit-root! + [zloc outfile] + (with-open [w (io/writer outfile :encoding "UTF-8")] + (z/print-root zloc w))) + + +(defn prep-workshop-code! + ([infile outfile] + (-> (z/of-file infile) + elide-comment-forms + (spit-root! outfile))) + ([infile indir outdir] + (prep-workshop-code! + (str indir infile) + (str outdir infile)))) + + +(comment + ;; Try one... + (prep-workshop-code! "ex06_full_functional_firepower.clj" + relative-src-path + relative-dest-path) + + ;; Review output using: + ;; ls -1 | grep ex | xargs -I {} diff -s {} "workshop/"{} + (doseq [f (sort (workshop-files! relative-src-path + workshop-file-pattern))] + (prep-workshop-code! f + relative-src-path + relative-dest-path)) + ) + + +(comment + ;; Experiment with file as EDN + (defn read-forms [file] + ;; https://stackoverflow.com/questions/39976680/reading-another-clojure-program-as-a-list-of-s-expressions + (let [rdr (-> file io/file io/reader PushbackReader.) + sentinel (Object.)] + (loop [forms []] + (let [form (edn/read {:eof sentinel} rdr)] + (if (= sentinel form) + forms + (recur (conj forms form))))))) + ) + +(comment + ;; Experiments with zippers + + ((comp z/child-sexprs z/down) (z/of-file "foobar.clj")) + + (spit-root! + (z/prewalk (z/of-file "foobar.clj") + #(= (z/value %) 'comment) + z/remove) + "foobar-cleaned.clj") + + + (let [zloc (z/of-file (str sourcedir "foobar.clj")) + outfile (str sourcedir "foobar-cleaned.clj")] + (with-open [w (clojure.java.io/writer outfile :encoding "UTF-8")] + (loop [znodes zloc] + (if (z/end? znodes) + (z/print-root znodes w) + (recur (if (= (z/value znodes) 'comment) ; for fanciness, this could be a comment macro by another name, like `extra-explanation' + ((comp z/next z/remove z/up) znodes) + (z/next znodes))))))) + ) diff --git a/src/clojure_by_example/workshop/.gitignore b/src/clojure_by_example/workshop/.gitignore new file mode 100644 index 0000000..7ddd3d9 --- /dev/null +++ b/src/clojure_by_example/workshop/.gitignore @@ -0,0 +1 @@ +*.clj diff --git a/src/clojure_by_example/workshop/README.txt b/src/clojure_by_example/workshop/README.txt new file mode 100644 index 0000000..8396100 --- /dev/null +++ b/src/clojure_by_example/workshop/README.txt @@ -0,0 +1,7 @@ +DO NOT commit clj files here. + +This directory is a target for generated clj source files. + +A reformatter will read "full featured" source meant for at-home use, +and dump an elided, leaner version here for in-class use (for the +teacher's convenience). From 2cb9e078251c81541f1a192c1946905d096f7c1d Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 1 Dec 2018 22:07:46 +0530 Subject: [PATCH 15/62] ex04 to teach API design with simple destructuring --- src/clojure_by_example/ex04_api_design.clj | 279 +++++++++++++-------- 1 file changed, 169 insertions(+), 110 deletions(-) diff --git a/src/clojure_by_example/ex04_api_design.clj b/src/clojure_by_example/ex04_api_design.clj index 1bf8e19..14eeb6b 100644 --- a/src/clojure_by_example/ex04_api_design.clj +++ b/src/clojure_by_example/ex04_api_design.clj @@ -1,20 +1,31 @@ -(ns clojure-by-example.ex04-api-design) +(ns clojure-by-example.ex04-api-design + (:require [clojure-by-example.data.planets :as p])) ;; EX04: Lesson Goals: ;; - See how to "de-structure" data (it's a powerful, flexible lookup mechanism) ;; - Leverage de-structuring to design a self-documenting function API -;; "De-structuring" +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; A tiny bit of "De-structuring" +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; - Clojure lets us reach into data structures in arbitrary ways, -;; and extract multiple values in one go -;; -;; - We use this for clean lookups in `let` bindings, and -;; in function signatures, to design expressive APIs. + +;; If we _shape_ our domain information as a data "structure", +;; can we use our knowledge of the shape to pull it apart; +;; i.e. "destructure" it? + +;; Yes. + +;; We use destructuring: +;; - In `let` bindings, to cleanly reach into data +;; - In function arguments, to make the API clean and expressive + + +;; Here are a couple of commonly-used ways to do it. -;; Positional De-structuring +;; "Positional" De-structuring ;; ;; - Pull apart sequential, ordered data structures like ;; lists, vectors, and any other sequence with linear access @@ -23,29 +34,23 @@ ;; bind values to symbols by position. -;; Instead of looking up values by index position: -(str "The first two planets: " - (:pname (get planets 0)) " and " - (:pname (get planets 1)) ".") +;; Evaluate and see what happens. Then form a theory of what might be going on. +(def planet-names + (map :pname p/target-planets)) -;; We can bind symbols by position (match structure to structure): -(let [[m, v] planets] - (str "The first two planets: " - (:pname m) " and " - (:pname v) ".")) +(let [[pname1 pname2] planet-names] + (str pname1 " is the 1st planet, and " + pname2 " is the 2nd planet.")) -;; Use underscores `_` to mark values we don't care for. -(let [[_, _, e] planets] - (str (:pname e) - " is the third rock from the Sun.")) +(let [[:as pnames] planet-names] + pnames) -;; Use `:as` to also alias the whole structure. -(let [[_, _, e :as planet-names] (map :pname planets)] +(let [[m v e :as pnames] (map :pname p/target-planets)] {:useless-trivia (str e " is the third rock from the Sun.") - :planet-names planet-names}) + :planets-names pnames}) ;; "Associative" De-structuring @@ -58,101 +63,155 @@ ;; - Follow the structure of the collection, and mechanically ;; bind values to symbols by key name. +;; Evaluate one by one and see what happens. +;; Then form a theory of what might be going on. -;; Instead of looking up values like this: -(:pname earth-alt) - -;; We can follow the map's structure like this: -(let [{p :pname} earth-alt] - p) - -;; Compose it with positional destructuring: -(let [[_, _, {e :pname}] planets] - (str e " is the third rock from the Sun.")) - - -;; And instead of doing lookups one at a time: -(str (:pname earth-alt) " has " - (:moons earth-alt) " moon, " - (:oxygen (:atmosphere earth-alt)) "% Oxygen, " - (:argon (:atmosphere earth-alt)) "% Argon, and " - (:traces (:atmosphere earth-alt)) "% trace gases.") - - -;; We can arbitrarily de-structure maps, directly: -(let [{p :pname - m :moons - {traces :traces - Ar :argon - O2 :oxygen} :atmosphere} earth-alt] - (str p " has " - m " moon, " - O2 "% Oxygen, " - Ar "% Argon, and " - traces "% trace gases.")) - - -;; The `:keys` form lets us de-structure more concisely: -;; - Note: in this style, we must exactly match spellings of -;; symbol names, with spellings of the keys we wish to bind. -(let [{:keys [pname moons] - {:keys [oxygen argon traces]} :atmosphere} - earth-alt] - (str pname " has " - moons " moon, " - oxygen "% Oxygen, " - argon "% Argon, and " - traces "% trace gases.")) - - -;; More powerfully, the `:keys` form lets us: -;; - extract multiple values, -;; - define fallbacks for missing values, _and_ -;; - alias the original input. -;; All in one shot: - -(defn summarise-planet - [{:keys [pname moons] - {:keys [oxygen argon traces] - :or {oxygen "unknown" - argon "unknown" - traces "unknown"}} :atmosphere - :as planet}] - {:summary (str pname " has " - moons " moon(s), " - oxygen " % O2, " - argon " % Argon, and " - traces" % of trace gases.") - :planet planet}) -(summarise-planet earth-alt) +(let [[mercury] p/target-planets] + (str (:pname mercury) " has mass " (:mass mercury))) -(summarise-planet {:pname "Mercury", :moons 0, :mass 0.0533}) -(map summarise-planet - planets) +(let [[{:keys [pname mass]}] p/target-planets] + (str pname " has mass " mass)) -;; Self-documenting function API: -;; -;; Last, but not least, de-structuring function arguments -;; automatically gives us self-documenting function signatures. +(let [[{:keys [pname mass] :as mercury}] p/target-planets] + (assoc mercury + :useless-trivia (str pname " has mass " mass))) -#_(clojure.repl/doc summarise-planet) ; Evaluate this in the REPL -#_(meta #'summarise-planet) -;; RECAP: +;; And, putting it all together, a function with a +;; more self-documented API: + +(defn add-useless-trivia + "Be Captain Obvious. Given a planet, add some self-evident trivia to it." + [{:keys [pname mass] :as planet}] + (assoc planet + :useless-trivia (str pname " has mass " mass))) + + +(map add-useless-trivia + p/target-planets) + +#_(clojure.repl/doc add-useless-trivia) + + +;; EXERCISE: +;; Review the de-structured argument list in `add-useless-trivia`; +;; then try to recall and reinforce a key concept. +;; Hints: +;; - Relate it to our preferred way to model the world. +;; - What is the "world" here? +;; - What are we using to model/describe what property of what? + + +;; EXERCISE: ;; -;; - hash-maps let us conveniently represent objects we wish to -;; model and query -;; - We can query hash-maps variously with keywords, `get`, and `get-in` -;; - If we use keywords as keys in hash-maps, querying is dead-simple -;; - We can define our own functions with `defn`, using this syntax: +;; - Use de-structuring to refactor the following functions +;; that we have copied over from ex03. ;; -;; (defn function-name -;; [arg1 arg2 arg3 ... argN] -;; (body of the function)) +;; - Develop a preliminary opinion about where and when +;; it might makes sense to de-structure, and where and when +;; it might not. + + +(def tolerances + "Define low/high bounds of planetary characteristics we care about." + {:co2 {:low 0.1, :high 5.0} + :gravity {:low 0.1, :high 2.0} + :surface-temp-deg-c {:low -125, :high 60}}) + + +(defn lower-bound + [tolerance-key] + (get-in tolerances [tolerance-key :low])) + + +(defn upper-bound + [tolerance-key] + (get-in tolerances [tolerance-key :high])) + + +(defn atmosphere-present? + [planet] + (not (empty? (:atmosphere planet)))) + + +(defn atmosphere-present?-refactored + [FIXME] + FIXME) + +#_(= (map :pname + (filter atmosphere-present? p/target-planets)) + (map :pname + (filter atmosphere-present?-refactored + p/target-planets))) + +(defn co2-tolerable? + [planet] + (let [co2 (get-in planet + [:atmosphere :carbon-dioxide])] + (when co2 + (<= (lower-bound :co2) + co2 + (upper-bound :co2))))) + + +(defn co2-tolerable?-refactored + [FIXME] + FIXME) + +#_(= (map :pname + (filter co2-tolerable? p/target-planets)) + (map :pname + (filter co2-tolerable?-refactored + p/target-planets))) + + +;; EXERCISE: +;; - Fix the body of the refactored function +;; - Carefully review the function APIs, and develop a preliminary +;; opinion whether the refactored version is better than the original. + + +(defn surface-temp-tolerable? + [planet] + (let [temp (:surface-temp-deg-c planet) + low (:low temp) + high (:high temp)] + (when (and low high) + (<= (lower-bound :surface-temp-deg-c) + low + high + (upper-bound :surface-temp-deg-c))))) + + +(defn surface-temp-tolerable?-refactored + [{:keys [FIXME] :as planet}] + FIXME) + + +(defn surface-temp-tolerable?-refactored-v2 + [{{:keys [low high]} :surface-temp-deg-c + :as planet}] + 'FIXME) + + +#_(= (map :pname + (filter surface-temp-tolerable? + p/target-planets)) + (map :pname + (filter surface-temp-tolerable?-refactored + p/target-planets)) + (map :pname + (filter surface-temp-tolerable?-refactored-v2 + p/target-planets))) + + +;; RECAP: ;; -;; - Using general-purpose data structures, and writing general-purpose -;; functions lets us do more with less +;; - We model the world by composing data structures and then use +;; "de-structuring" to conveniently reach into those structures. +;; - When, where, and how much to de-structure is a matter of +;; taste; a design choice. There is no One True Way. From 81d4e528889856cf964ae8f0349ebad5c84d52be Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sun, 30 Dec 2018 21:42:51 +0530 Subject: [PATCH 16/62] Improve ex00 copy & add commentary for home use --- src/clojure_by_example/ex00_introduction.clj | 132 +++++++++++++------ 1 file changed, 91 insertions(+), 41 deletions(-) diff --git a/src/clojure_by_example/ex00_introduction.clj b/src/clojure_by_example/ex00_introduction.clj index 15e4422..fd19f38 100644 --- a/src/clojure_by_example/ex00_introduction.clj +++ b/src/clojure_by_example/ex00_introduction.clj @@ -9,13 +9,15 @@ ;; EX00: LESSON GOAL: -;; - Drill some Clojure basics, to set up the sections -;; to follow (and generally, to help grok code in the wild) +;; - Drill some Clojure basics, required for the sections +;; that follow (and generally, to help follow code +;; in the wild) ;; - Familiarize one's eyes with Clojure syntax ;; - Understand Clojure's evaluation model ;; - Start using an interactive development workflow -;; right away - +;; right away --- this is what it means to be a +;; "Dynamic" programming language (not to be confused +;; with dynamically typed languages.) ;; All Clojure code is composed of "expressions": @@ -59,9 +61,13 @@ reduce ; transform a collection ;; Namespaces: ;; ;; - are how we organize and/or modularise code -;; - all Clojure code is defined and evaluated within namespaces +;; - All Clojure code is defined and evaluated within namespaces + -;; Evaluate and see: +;; EXERCISE: +;; Evaluate the following expressions and see what you get. +;; - First, type the expression in the REPL +;; - Next, evaluate them straight from your codebase map ; is defined in the `clojure.core` ns (namespace) @@ -69,14 +75,35 @@ same ; is defined in the current ns #_(ns-name *ns*) ; What's the current ns? +(comment + ;; PROTIP: + ;; + ;; Your IDE or text editor would have a convenient shortcut to + ;; evaluate any Clojure expression right from your codebase. + ;; + ;; Some editors allow you to "evaluate in-line", some would + ;; tell you to "send to the REPL". Consult the documentation + ;; that accompanies your editor's Clojure plugin, to learn + ;; how to do this. + ;; + ;; Make a habit of interacting "dynamically" with Clojure + ;; this way, right from inside your codebase; i.e. prefer + ;;_not_ to type things directly into the REPL. + ) + + ;; Clojure expression syntax rules: ;; - Literals: ;; - Just write them down -;; - Collections and S-expressions: -;; - Always. Be. Closing. +;; - Collection literals and s-expressions: +;; - ABC - Always. Be. Closing. :-D +;; - The Clojure "Reader" (the 'R' part of the R.E.P.L) +;; expects each open bracket to be accompanied by a +;; corresponding closing bracket. i.e. all parentheses +;; must be "balanced". ;; [1 2 3] ; OK ;; [1 2 3 ; FAIL @@ -109,18 +136,18 @@ same ; is defined in the current ns ;; Clojure Expression Evaluation Rules: -(+ 1 2) +;; - Wrap in parentheses to cause evaluation. +;; The first position is special, and must be +;; occupied by a function -;; - Wrap in parentheses to cause evaluation +(+ 1 2) ; OK +;; (1 2) ; FAIL, because 1 is not a function -;; - First position is special, and must be occupied by a function -;; (1 2) ; FAIL, because 1 is not a function +;; - Mentally evaluate nested expressions "inside-out". +;; Usually, all s-expressions--however deeply nested--evaluate +;; to a return value; a literal, or a collection, or a function, +;; or some legal object. -;; - All s-expressions, however deeply nested, finally evaluate -;; to a return value (a literal, or a collection, or a function.) - -;; - Mentally evaluate nested expressions "inside-out": -;; (+ (+ (+ 1 2) (+ 1 2)) (+ (+ 1 2) (+ 1 2))) @@ -132,22 +159,43 @@ same ; is defined in the current ns 12 -;; - _Prevent_ evaluation of s-expression by "quoting" it, -;; i.e. explicitly marking a list: +;; - _Prevent_ evaluation of s-expr by "quoting" it, +;; i.e. explicitly marking a list, by prefixing it +;; with a single quote `'`: + +'(+ 1 2) ; BUT the list will still remain in the evaluation path -'(+ 1 2) ; but the list will still remain in the evaluation path +;; - Leave an s-expression in-line, but remove it from +;; the evaluation path, by prefixing it with `#_`: -;; - Prevent evaluation and _elide_ any s-expression -;; (the special "#_" syntax is called a "reader macro"). +(+ 1 2 #_(+ 1 2)) ; will evaluate to 3 -(+ (+ (+ 1 2) (+ 1 2)) - #_(+ (+ 1 2) (+ 1 2))) ; elide the nested sub-expression from execution path +;; - Comment out entirely, by prefixing code with one or more +;; semicolons, just like in-line comments. -;; - Entirely _ignore_ code and free-form text by commenting out -;; with one or more semicolons: -; ( + 1 2 -;; 3 4 -;;; 5 6) +;; (+ (+ 1 2) +;; (+ 1 2)) ; fully commented out + + +;; EXERCISE: +;; - Now, why will the following expression fail (throw an exception)? +;; Make an educated guess, then try it. + +;; (+ 1 2 '(+ 1 2)) ; un-comment and evaluate; then comment it back + + +(comment + ;; PROTIP: + ;; + ;; The special "#_" syntax is called a "reader macro". + ;; + ;; For now, ignore what that means, just know the effect of + ;; using it. You will see #_ often in code to follow. + ;; + ;; Incidentally, the single quote we used '(to mark a list) + ;; is also a reader macro. Many more specialized reader macros + ;; are available, but don't go there just yet. + ) @@ -171,16 +219,18 @@ same ; is defined in the current ns ;; What does it look like? ;; - Let's flatten it into one line for illustrative purposes: - -;;[1] [2] [3] [4] +;;[1] [2] [3] [4] (defn hie [person message] (str "Hie, " person " : " message)) ; [5] -;; Where: -;; - [1] `defn` is a Clojure built-in primitive -;; - Notice, it's at the 1st position, and -;; - 2-4 are all arguments to defn -;; Further: -;; - [2] is a Clojure symbol, `hello`, which will name the function -;; - [3] is a Clojure vector of two named arguments -;; - [4] is a Clojure s-expression, and is treated as the body of -;; the function definition -;; - [5] the whole thing itself is a Clojure s-expression! + +(comment + ;; Here: + ;; - [1] `defn` is a Clojure built-in primitive + ;; - Notice, it's at the 1st position, and + ;; - 2-4 are all arguments to defn + ;; Further: + ;; - [2] is a Clojure symbol, `hello`, which will name the function + ;; - [3] is a Clojure vector of two named arguments + ;; - [4] is a Clojure s-expression, and is treated as the body of + ;; the function definition + ;; - [5] the whole thing itself is a Clojure s-expression! + ) From dc5a9d22a60a86841f79f76f8e5901560494aa8e Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 5 Jan 2019 17:12:53 +0530 Subject: [PATCH 17/62] Add recap section to ex00 --- src/clojure_by_example/ex00_introduction.clj | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/clojure_by_example/ex00_introduction.clj b/src/clojure_by_example/ex00_introduction.clj index fd19f38..b898657 100644 --- a/src/clojure_by_example/ex00_introduction.clj +++ b/src/clojure_by_example/ex00_introduction.clj @@ -234,3 +234,16 @@ same ; is defined in the current ns ;; the function definition ;; - [5] the whole thing itself is a Clojure s-expression! ) + + +;; RECAP: +;; +;; - All Clojure code is a bunch of "expressions" +;; (literals, collections, s-expressions) +;; +;; - All Clojure expressions evaluate to a return value +;; +;; - All Clojure code is written in terms of its own data structures +;; +;; - All opening braces or parentheses must be matched by closing +;; braces or parentheses, to create legal Clojure expressions. From b059719ffb7c6a4bdc742a436b8eff61056ee171 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 5 Jan 2019 17:13:44 +0530 Subject: [PATCH 18/62] Exercisify ex01 & improve questions, commentary --- .../ex01_fundamentally_functional.clj | 253 +++++++++++++----- 1 file changed, 193 insertions(+), 60 deletions(-) diff --git a/src/clojure_by_example/ex01_fundamentally_functional.clj b/src/clojure_by_example/ex01_fundamentally_functional.clj index e53b651..b27feff 100644 --- a/src/clojure_by_example/ex01_fundamentally_functional.clj +++ b/src/clojure_by_example/ex01_fundamentally_functional.clj @@ -5,6 +5,17 @@ ;; are the bedrock upon which Clojure programs are built ;; - Drill how to use functions, and how lexical scope works ;; - Get comfortable with how functions compose together +;; - At every step, further drill the interactive REPL workflow. +;; Figure out how to take advantage of the immediate feedback +;; that the live REPL gives you. Treat each exercise as a +;; tiny experimental setup. Run small experiments that will +;; help you discover answers... +;; -> Read the exercise +;; -> Make a testable guess (hypothesis) +;; -> Evaluate your solution (test your hypothesis) +;; -> Compare your guess with the solution +;; -> If it differs, update your guess (or fix the solution) and redo +;; That is, try to use the Scientific Method to solve exercises. ;; Basic Function Syntax @@ -49,9 +60,10 @@ (same {:pname "Earth" :moons 1}) - +;; EXERCISE ;; How about the anonymous version of `same`? -;; - What's the evaluation model? +;; - What's the evaluation model? Think before you tinker. +;; - Form your hypothesis -> Test it -> Learn from the feedback ((fn [x] x) 42) @@ -60,7 +72,12 @@ ((fn [x] x) {:pname "Earth" :moons 1}) -;; What should happen here? +;; EXERCISE +;; Fix the the following s-expr, so that it evaluates to true. +;; - First predict the solution in your head. +;; - Then replace 'FIX with your solution and evaluate to confirm. +;; - Think about the little experiment you just performed, and +;; form a theory about why the solution worked (= 'FIX (same same) ((fn [x] x) same)) @@ -72,30 +89,83 @@ ;; - is extremely general (accepts any value) ;; - is surprisingly useful, as we will discover later -;; Fix this to prove `identity` and `same` are the same: +;; EXERCISE +;; Fix this to prove `identity`, `same`, and the anonymous +;; version of `same`, all do the exact same thing: ;; - Note: Functions are values and can therefore be compared. ;; (= identity - (same identity) - ((fn [x] x) identity) - (identity identity)) + ('FIX identity) + ('FIX identity) + ('FIX identity)) ;; ;; Now, evaluate this in the REPL to _see_ the truth: -;; (clojure.repl/source identity) +;; +#_(clojure.repl/source identity) +;; +(comment + ;; This is another example of what "dynamic" means. + ;; We can not only can we interact live with small bits of + ;; our Clojure programs, we can also examine many aspects + ;; of our programs at run time. The clojure.repl namespace + ;; is one tool at our disposal. Try these in the REPL: + #_(clojure.repl/dir clojure.repl) + #_(clojure.repl/doc clojure.repl) + ) +;; "Higher order" functions (HoFs): -;; And to round it up... functions can accept, as well as -;; return functions. +;; Functions that: +;; - *accept* functions as arguments +;; and/or +;; - *return* functions as results +;; are called "higher order" functions. + + +;; EXERCISE +;; Have we seen HoFs so far? If yes, list them out below. + + +;; EXERCISE +;; Write a zero-argument function that returns the `identity` function (defn gen-identity [] ; zero arguments - identity) + 'FIX) +;; EXERCISE +;; Fix this function so that it returns a function that _behaves_ +;; like the identity function (don't return `same`, or `identity`). + +(defn gen-identity-v2 + [] + 'FIX) + +;; EXERCISE +;; Replace 'FIX1 with a call to the `gen-identity` function, +;; and 'FIX2 with a call to the `gen-identity-v2` function, +;; such that the following evaluates to true. (= identity - (gen-identity)) + 'FIX1 + 'FIX2) + + +;; Composing Logic with Higher-order Functions (HoFs): +(comment + ;; Clojure programmers often write simple functions that + ;; each do one task well, and use higher order functions + ;; to "compose" these in creative ways, to produce more + ;; useful pieces of logic. + ;; + ;; We treat "simple" functions as building blocks, and + ;; HoFs as versatile mini-blueprints that help us organize + ;; and glue together the simple functions. + ) +;; EXERCISE +;; Reason about why this is working: (defn selfie "Given a function `f`, return the result of @@ -103,7 +173,6 @@ [f] (f f)) - (= 42 (identity 42) ((selfie identity) 42) @@ -111,27 +180,56 @@ ((selfie (selfie (selfie identity))) 42)) ; ad-infinitum -;; Compose (chain) functions with `comp` -((comp inc inc inc) 39) +;; Let's play with a couple of nifty HoFs built into Clojure +;; - `comp` +;; - `complement` -;; Negate predicates with `complement` -((complement string?) "hi") -;; Use `fnil` to "nil-patch" functions that cannot sanely handle nil (null) inputs -#_(+ nil 1) ; FAIL -((fnil + 0) nil 1) ; OK +;; EXERCISE +;; Use `(comp vec str inc)` to make the following true +;; - `comp` accepts any number of functions as arguments, +;; and returns a function that behaves as a pipeline +;; (or chain) of the given functions + +(= [\4 \2] + (vec (str (inc 41))) + ('FIX 'FIX)) + +(comment + ;; Reason about the order of evaluation and how inputs + ;; and outputs should connect, for `comp` chains to + ;; work correctly. + ;; + ;; To see if you reasoned correctly, try each of + ;; seq, str, inc independently: + (inc 41) ; increment a number + (str 42) ; turn the input into a string + (seq "42") ; turns a string into a character sequence + ) + -;; Juxtapose functions with `juxt` (place results "side-by-side") -((juxt inc identity dec) 42) +;; EXERCISE +;; Use `(complement string?)` to make the following true +;; - `complement` accepts a "predicate" function, and returns a +;; function that does the opposite of the given "predicate" +(= (not (string? "hi")) + ('FIX 'FIX)) +(comment + ;; "Predicate" is just a term we use to conveniently describe + ;; any function that returns a truthy/falsey value, i.e. + ;; any function that is used to test for some condition. + ;; These so-called "predicates" are not inherently special. +) -;; Lexical Scope in Clojure +;; "Lexical Scope" in Clojure +;; - Develop an intuition for what "Lexical scope" might mean +;; by reasoning about the following exercises. ;; EXERCISE: -;; Reason about the following expressions. -;; - Mentally evaluate and predict the results; then check. +;; Mentally evaluate and predict the results; then check. (def x 42) ; Bind `x` to 42, globally ("top-level" binding) @@ -139,11 +237,15 @@ ((fn [x] x) x) ; also returns 42, but how? -(let [x 10] ; we use `let` to bind things locally - (+ x 1)) +(let [x 10] ; We use `let` to bind things locally. + x) ; This evaluates to the value of the "let-bound" `x`. +(+ (let [x 10] + x) + x) ; So, this whole thing should evaluate to what? -;; EXERCISE: + +;; EXERCISE ;; Read carefully, and compare these three function variants: (defn add-one-v1 @@ -171,9 +273,11 @@ (add-one-v3 x) ; should evaluate to what? -;; `let` -;; - Mentally evaluate these, predict the results, +;; EXERCISE +;; - Mentally evaluate the following, predict the results, ;; and try to infer the scoping rule. +;; - Then evaluate each expression to see if your +;; mental model agrees with the result you see. ;; - Start with any `x`, and mechanically work ;; your way around. @@ -189,39 +293,72 @@ ((let [x 10] (fn [x] x)) x) +(comment + ;; Strict lexical scope greatly simplifies our life, because + ;; it allows us to mechanically work out where a value originated. + ;; - Start at the place of reference of the value. + ;; - Then "walk" outwards, until you meet the very first let binding, + ;; or argument list, or def, where the value was bound. + ;; - Now you know where the value came from. + ;; + ;; This also helps reduce our mental burden of inventing new names + ;; to refer to things, because we can re-use a name within a + ;; limited scope, and be certain that it will not destroy + ;; anything with the same name outside the given scope. + ) -;; Function Closure: -;; - Read carefully and work out what gets bound to `scale-by-PI`. -(defn scale-by - "Given a number `x`, return a function that accepts - another number `y`, and scales `y` by `x`." - [x] - (fn [y] (* y x))) +;; Function "Closure" +;; - This is a way for a function to capture and "close over" +;; any value available at the time the function is defined (def PI 3.141592653589793) -((scale-by PI) 10) +(defn scale-by-PI + [n] + (* n PI)) ; PI is captured within the body of `scale-by-PI` +(scale-by-PI 10) -;; Achieve the same with `partial` application -(def scale-by-PI (partial * PI)) -(scale-by-PI 10) +;; A more general way to "scale by": +;; - Thanks to lexical scope + the function closure property + +(defn scale-by + "Given a number `x`, return a function that accepts + another number `y`, and scales `y` by `x`." + [x] + (fn [y] (* y x))) ; whatever is passed as `x` is captured + ; within the body of the returned function + + +;; EXERCISE +;; +#_(= (scale-by-PI 10) + ('FIX 10) + (* PI 10)) +(comment + ;; BONUS EXERCISES + ;; Define a few scaling functions, in terms of `scale-by` + ;; + (def scale-by-PI-v2 + 'FIX) -;; Strict lexical scope allows us to mechanically work out -;; where a value originated. -;; - Start at the place of reference of the value. -;; - Then "walk" outwards, until you meet the very first let binding, -;; or argument list, or def, where the value was bound. -;; - Now you know where the value came from. + (def quadruple + "4x the given number." + 'FIX) + (def halve + 'FIX)) -;; Sequences (or Collections), and operations on Sequences +;; Sequences (or Collections) +;; +;; - and operations on Sequences ;; - Clojure provides _many_ sequence functions. -;; Here are some important ones: +;; Here are some important ones: `map`, `filter`, and `reduce` +;; - Observe that all these functions are HoFs! map ;; Basic Syntax: @@ -313,14 +450,10 @@ reduce -;; RECAP: -;; -;; - All Clojure code is a bunch of "expressions" -;; (literals, collections, s-expressions) -;; -;; - All Clojure expressions evaluate to a return value -;; -;; - All Clojure code is written in terms of its own data structures -;; -;; - All opening braces or parentheses must be matched by closing -;; braces or parentheses, to create legal Clojure expressions. +;; RECAP +;; - Acquire a "scientific experimentation" mindset when +;; interactively developing and debugging Clojure code +;; ... The REPL is your friend. +;; - Learn to use lexical scope and function closures effectively. +;; - Learn to define small "single purpose" functions, such that +;; you can compose them together to produce higher order logic. From 2a5aabd031d6124bede12e775e08380e4f933434 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 5 Jan 2019 21:08:16 +0530 Subject: [PATCH 19/62] Exercisify ex02 and improve explanations --- .../ex02_domain_as_data.clj | 86 +++++++++++++------ 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/src/clojure_by_example/ex02_domain_as_data.clj b/src/clojure_by_example/ex02_domain_as_data.clj index 948c7eb..89a8da6 100644 --- a/src/clojure_by_example/ex02_domain_as_data.clj +++ b/src/clojure_by_example/ex02_domain_as_data.clj @@ -66,23 +66,30 @@ :argon 0.33 :traces 0.14}}) +;; EXERCISE ;; `get` and `get-in` work as expected +;; - Use `get` to extract :traces from `earth-alt`'s atmosphere +;; - The use `get-in` to do the same -(get (get earth-alt :atmosphere) - :traces) +#_(get 'FIX + 'FIX) +#_(get-in 'FIX 'FIX) -(get-in earth-alt [:atmosphere :traces]) - -;; BUT, unlike plain old strings, keywords also behave as _functions_ -;; of hash-maps, and can look themselves up in hash-maps. +;; BUT, unlike plain old strings, keywords also behave as +;; _functions_ of hash-maps, and can look themselves up +;; in any given hash-map. ;; ("pname" earth) ; Will FAIL! (:pname earth-alt) ; Works! -(:argon (:atmosphere earth-alt)) + +;; EXERCISE +;; Extract `:argon` from the `:atmosphere` of `earth-alt` + +('FIX ('FIX earth-alt)) ;; Which means we can use keywords in this manner: @@ -103,42 +110,64 @@ planets) -;; Find planets with less mass than the Earth +;; EXERCISE +;; `filter` out planets with less `:mass` than the Earth (defn less-mass-than-earth? [planet] - (< (:mass planet) 1)) + (< ('FIX planet) 1)) + +('FIX 'FIX 'FIX) + + +;; EXERCISE +;; Recall how to use `filter`, `map`, and `reduce`: +(filter even? [1 2 3 4]) +(map inc [1 2 3 4]) +(reduce + 0 [1 2 3 4]) +;; Use these to compute the total `:mass` of planets +;; having less mass than the Earth. + -(filter less-mass-than-earth? - planets) +;; Maps, Vectors, and Sets also behave like functions! +;; - We don't normally use maps and vector in the function +;; position to perform lookups (there are a few problems +;; with doing so), but we often use _well-defined_ sets as +;; predicate functions, to test for set membership. -;; Compute total mass of planets having -;; less mass than the Earth: -(reduce + 0 - (map :mass - (filter less-mass-than-earth? - planets))) +;; Maps can "self-look-up" keys +({:a "a", :b "b"} :a) -;; Collections like Maps, Vectors, and Sets can behave like functions -;; - We normally don't use Vectors and Maps like this, but -;; we often use sets as predicate functions: +;; Vectors can "self-look-up" by index position -({:a "a", :b "b", :c "c"} :c) ; key-value lookup +(["a" "b" "c"] 0) -(["a" "b" "c"] 0) ; index lookup +;; Sets can self-test set membership -(#{"a" "b" "c"} "b") ; set membership +(#{"a" "b" "c"} "b") ; truthy: return set member if it exists +(#{"a" "b" "c"} "boo") ; falsey: return `nil` if it doesn't + +;; Lists do NOT behave like functions + +#_('("a" "b" "c") 0) ; FAIL + + +;; EXERCISE +;; Define a predicate `poison-gas?` which returns the +;; poison gas if it belongs to a set of known poison gases, +;; or `nil` (falsey) otherwise. These are some known poison gases: +:carbon-monoxide, :chlorine, :helium +:sulphur-dioxide, :hydrogen-chloride (def poison-gas? "Does the given gas belong to a set of known poison gases?" - #{:carbon-monoxide, :chlorine - :sulphur-dioxide, :hydrogen-chloride}) + 'FIX) -(poison-gas? :oxygen) ; falsey (poison-gas? :chlorine) ; truthy +(poison-gas? :oxygen) ; falsey ;; Collections are "open", i.e. very flexible @@ -160,8 +189,9 @@ [str identity inc dec (fn [x] x)]) -;; We use the flexibility of collections, to model -;; real-world objects and logic as we please +;; Domain Modeling in Clojure +;; - We use the flexibility of collections, to model +;; real-world objects and logic as we please ;; Predicates and operations From 9c55e43cbef8a7442e8f2bc731a47bc3313a1b2a Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 5 Jan 2019 21:23:47 +0530 Subject: [PATCH 20/62] Bump Clojure version to 1.10.0 --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index b1ad648..cf35f51 100644 --- a/project.clj +++ b/project.clj @@ -4,7 +4,7 @@ :license {:name "MIT" :url "https://opensource.org/licenses/MIT"} ;; Requirements: Java 8 or higher (recommended: Java 8 or Java 11) - :dependencies [[org.clojure/clojure "1.10.0-RC2"]] + :dependencies [[org.clojure/clojure "1.10.0"]] :profiles {:dev {:dependencies [[org.clojure/data.json "0.2.6"] [enlive "1.1.6"] [rewrite-clj "0.6.1"]]}}) From f3734d54fad609214b06b970400ff9bd571e0e75 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 5 Jan 2019 22:53:11 +0530 Subject: [PATCH 21/62] Update README for IntelliJ + Cursive instead of Lighttable, which we have to sadly drop as it is unmaintained, and we cannot recommend it any more. --- README.md | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 61b43c4..eae3364 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ First, make sure you have Java 8. Notes: - - If you have Java 9, that should be OK too. + - If you have Java 9+, or Open JDK 9+ that should be OK too. - The LightTable editor is known to break with Java 9. Use Java 8 instead. - We have not tested this project with Java 7. @@ -87,39 +87,38 @@ Note: Set up an editor and figure out how to evaluate Clojure code with it. -### LightTable +### IntelliJ + Cursive IDE -We used LightTable for our workshop. We suggest you do so too, unless of course, you have already set up your favourite editor for Clojure development. Avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! +We support IntelliJ + Cursive IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. Avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! - - You may install LightTable from the [official website](http://lighttable.com/). - - But you must have Java 8. LightTable breaks with Java 9. - - On Mac OS, you may have to allow running the app in your security preferences to be able to open it. + - Download and Install [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) + - Install and configure the Cursive plugin for IntelliJ by followint the [official Cursive user guide](https://cursive-ide.com/userguide/). Once installed: - - Use LightTable's file menu to open this project. - - In the left pane, navigate down to the first file `ex00...`, under the `src` folder. - - Under `View` menu, click `Connections`. A right pane should open. - - Under `Add Connection`, click `Clojure (remote REPL)` and complete the port number. Recall host:port information was printed to the terminal when you fired up a REPL in the previous section. - - In the `ex00..` file, scroll down a little, till you see `(+ 1 2)`. - - Place your cursor after the closing parenthesis `)` and hit Ctrl+Enter (Win/Linux), or Cmd+Enter (Mac). - - You should see `3` appear in-line. This means you successfully connected and evaluated an expression. + - Launch IntelliJ and select "Import Project" from the opening splash screen. + - OR use IntelliJ's file menu to open this project via File > New > Project From Existing Sources + - Select this project's main directory; click OK + - The "Import Project" dialog box should open + - Select Leiningen under "Import project from external model"; click Next + - Click Next again in the following screen that shows "Root Directory"; wait for it... + - Again, click Next in the screen that says "Select Leiningen projects to import" + - And again, click Next in the "Please select project SDK" screen (ensure you select JDK version 1.8 or higher) + - Click "Finish", and wait for IntelliJ to set up the project + - In the left pane, navigate down to the `project.clj` file, under the project's root folder. + - Right click on `project.clj` and select the option that says "Run REPL for ..." + - A right pane should open, with a REPL session. + - Now, open the `ex00..` file under the `src` folder, scroll down a little, till you see `(+ 1 2)`. + - Place your cursor after the closing parenthesis `)`, then right-click to open the context menu, and click on REPL > "Send '(+ 1 2)' to the REPL. + - You should see '(+ 1 2)' appear in the REPL window, followed by `3`. This means you successfully evaluated an expression in the REPL. - Now you may start from the top of ex00 and work through the material. -Also keep [LightTable's documentation](http://docs.lighttable.com/tutorials/full/) handy in case you need editor help, as you solve the workshop material. - - -Optionally, add Parinfer for easier editing: - - - In LightTable, go to View -> Plugin Manager and search for "parinfer". - - Install the Parinfer plugin by Maurício Szabo. - - Parinfer is an editing system for Clojure that makes it easy for you to move Clojure code around without unbalancing parentheses. - - We recommend going through the [Parinfer documentation here](https://shaunlebron.github.io/parinfer/). But don't get stuck there, just keep it handy. +Also keep the Cursive user guide handy, in case you need editor help, as you solve the workshop material. In particular, the [Paredit guide](https://cursive-ide.com/userguide/paredit.html) may be useful, if you stumble when editing Clojure code. ### Alternative Starter Kits: -If you can't use LightTable for some reason (like can't downgrade to Java 8 from Java 9). You may try one of these. Although we haven't tested with these setups, the workshop material should work fine. +If you can't use IntelliJ for some reason, you may try one of these. Although we haven't tested with these setups, the workshop material should work fine. - A [snazzy setup with Atom](https://medium.com/@jacekschae/slick-clojure-editor-setup-with-atom-a3c1b528b722). - Brave Clojure walks you through [a basic Emacs setup for learning Clojure](https://www.braveclojure.com/basic-emacs/). From 549e7118e6346c0b2e3f7603f2b8142ca24ced28 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 5 Jan 2019 22:55:25 +0530 Subject: [PATCH 22/62] gitignore IntelliJ-generated .idea directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e436df5..8203bfd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ pom.xml.asc .hg/ .setup.sh profiles.clj +.idea From 1e4d5d795ddd092712984a31efbd9459bfe7e4cf Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sun, 6 Jan 2019 19:52:51 +0530 Subject: [PATCH 23/62] Exercisify ex03; intended as a reading exercise to tie together, and reinforce all the concepts seen from ex00 through to ex02. --- .../ex03_data_and_functions.clj | 93 ++++++++++++++++--- 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index fd02672..cb070f8 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -4,9 +4,79 @@ ;; Ex03: LESSON GOALS -;; - Use stuff we've seen so far to build purely functional logic -;; to process a bunch of planets +;; - Primarily, a code reading exercise +;; - Explore various bits and bobs of the solution interactively +;; using the live environment at your disposal +;; - Get some ideas of how to take just a handful of pieces, +;; and build sophisticated logic with them +;; - We use only the concepts and standard library functions +;; we've seen so far, to build purely functional logic +;; in order to process a bunch of planets: +;; +;; - Standard Library (about 20 functions): +;; `def`, `defn`, `let` -- to define/name simple data and small functions +;; `get`, `get-in`, `assoc` -- to query and associate data +;; `map`, `filter`, `reduce` -- to operate on collections +;; `if`, `when`, `cond` -- to decide things +;; `not`, `and`, `empty?`, `<=`, `count` -- for logic and quantities +;; `comp`, `complement` -- to glue higher-order logic +;; +;; - Concepts: +;; - Compute only with pure functions: +;; - Build higher-order logic with higher order functions +;; - Lexical scope and function closures for +;; - Collections as functions: +;; - Keywords as functions of hash-maps +;; - Well-defined Sets as predicates --- tests of set membership +;; - Hash-maps and collections to model domain entities: +;; - A planet, or atmospheric tolerances, or decision tables, +;; or collections of analysis criteria +;; - Truthy / Falsey logic: +;; - Instead of only Boolean true/false +;; - Namespaces: +;; - Making use of things defined elsewhere +;; +;; - Workflow: +;; - Apply the Scientific Method to design, debug, and to understand +;; - Run small fast experiments via the REPL +;; - Preserve your experiments in-line within your codebase itself +;; +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Let's colonize planets! +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(comment + ;; BACKGROUND + ;; + ;; The Office of Interstellar Affairs (OIA) is pushing hard + ;; for all-out space exploration and colonization. + ;; + ;; The OIA intends to issue "mission directives"... + ;; + ;; They wish humanity to :inhabit, or :colonise, or :probe, + ;; or :observe a given planet based on their analysis of + ;; available planetary data. + ;; + ;; For a given "mission directive", like :probe, the OIA + ;; intends to dispatch a collection of vessels. + + ;; GOAL + ;; + ;; Prototype a bit of planetary analysis logic, using criteria + ;; that interest the OIA, such that they will be able to decide + ;; what to do about a given planet. + ;; + ;; Criteria include questions like: + ;; - co2-tolerable? + ;; - gravity-tolerable? + ;; - surface-temp-tolerable? + ;; + ;; How a planet stands up to such questions will let us assess + ;; whether it is habitable? or colonisable? or observe-only?. + ;; + ;; Once we deliver the OIA our assessment, they may choose to + ;; dispatch one or more kinds of Starfleet vessels to the planet. + ) ;; Here are some target planets: clojure-by-example.data.planets/target-planets @@ -18,16 +88,11 @@ p/target-planets -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Let's colonize planets! -;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - (def starfleet-mission-configurations - "Associate mission directives and mission configurations of Starfleet vessels. - - The Office of Interstellar Affairs (OIA) issues mission directives - based on its analysis of planets." + "Associate 'mission directives' like :inhabit, :colonise, :probe, + and 'mission configurations' of Starfleet vessels. e.g. If our + analysis of a planet says :probe, then we would send 1 'Orbiter' + class Starship carrying a complement of 100 autonomous probes." {:inhabit {:starships 5, :battle-cruisers 5, :orbiters 5, :cargo-ships 5, @@ -178,8 +243,7 @@ p/target-planets (defn planet-meets-no-condition? [conditions planet] - ((comp zero? count conditions-met) - conditions planet)) + (empty? (conditions-met conditions planet))) (def planet-meets-any-one-condition? @@ -268,5 +332,4 @@ p/target-planets :mission-vessels (mission-directive starfleet-mission-configurations)))) -#_(map (juxt :pname assign-vessels) - p/target-planets) +#_(map assign-vessels p/target-planets) From 97f1eaefac96bbdf4aa7799887cb2100e000cc48 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 7 Jan 2019 19:39:42 +0530 Subject: [PATCH 24/62] Improve ex01 explanation of lexical scope --- .../ex01_fundamentally_functional.clj | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/clojure_by_example/ex01_fundamentally_functional.clj b/src/clojure_by_example/ex01_fundamentally_functional.clj index b27feff..c8a7d6d 100644 --- a/src/clojure_by_example/ex01_fundamentally_functional.clj +++ b/src/clojure_by_example/ex01_fundamentally_functional.clj @@ -225,11 +225,30 @@ ;; "Lexical Scope" in Clojure -;; - Develop an intuition for what "Lexical scope" might mean -;; by reasoning about the following exercises. +;; - Lexical scope guarantees that the reference to a value will be +;; "enclosed" in the scope in which it is being used. + +(comment + ;; Strict lexical scope greatly simplifies our life, because + ;; it allows us to mechanically follow code, and determine + ;; where a value originated. + ;; - Start at the place of reference of the value. + ;; - Then "walk" outwards, until you meet the very first let binding, + ;; or arg-list, or def, where the value was bound. + ;; - Now you know where the value came from. + ;; + ;; This also helps reduce our mental burden of inventing + ;; new names to refer to things, because we can re-use + ;; a name within a limited scope, and be certain that + ;; it will not destroy anything with the same name outside + ;; the given scope. + ) ;; EXERCISE: -;; Mentally evaluate and predict the results; then check. +;; - Develop an intuition for what "Lexical scope" might mean +;; by reasoning about the following exercises. +;; +;; - Mentally evaluate and predict the results; then check. (def x 42) ; Bind `x` to 42, globally ("top-level" binding) @@ -293,21 +312,6 @@ ((let [x 10] (fn [x] x)) x) -(comment - ;; Strict lexical scope greatly simplifies our life, because - ;; it allows us to mechanically work out where a value originated. - ;; - Start at the place of reference of the value. - ;; - Then "walk" outwards, until you meet the very first let binding, - ;; or argument list, or def, where the value was bound. - ;; - Now you know where the value came from. - ;; - ;; This also helps reduce our mental burden of inventing new names - ;; to refer to things, because we can re-use a name within a - ;; limited scope, and be certain that it will not destroy - ;; anything with the same name outside the given scope. - ) - - ;; Function "Closure" ;; - This is a way for a function to capture and "close over" ;; any value available at the time the function is defined From 0e8f18cfb5cb70772db729af08b0f0444a189030 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 7 Jan 2019 20:05:12 +0530 Subject: [PATCH 25/62] Fix ex04 explanations, & include multiple arities Multi-arity functions are an important tool for clean design of function apis, and seen pervasively in the wild. --- src/clojure_by_example/ex04_api_design.clj | 162 +++++++++++++++++---- 1 file changed, 130 insertions(+), 32 deletions(-) diff --git a/src/clojure_by_example/ex04_api_design.clj b/src/clojure_by_example/ex04_api_design.clj index 14eeb6b..8401b75 100644 --- a/src/clojure_by_example/ex04_api_design.clj +++ b/src/clojure_by_example/ex04_api_design.clj @@ -2,29 +2,138 @@ (:require [clojure-by-example.data.planets :as p])) ;; EX04: Lesson Goals: +;; - We use these conveniences for good effect in API design. +;; - See how to allow the same function to support different arities, +;; as well as a variable number of arguments ;; - See how to "de-structure" data (it's a powerful, flexible lookup mechanism) ;; - Leverage de-structuring to design a self-documenting function API ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; A tiny bit of "De-structuring" +;; +;; Multiple arities +;; +;; - When we know for sure that a function must handle more than +;; one "arity". An "arity" is the number of arguments +;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; If we _shape_ our domain information as a data "structure", -;; can we use our knowledge of the shape to pull it apart; -;; i.e. "destructure" it? +(defn add-upto-three-nums + ([] 0) ; identity of addition + ([x] x) + ([x y] (+ x y)) + ([x y z] (+ x y z))) + +(add-upto-three-nums) +(add-upto-three-nums 1) +(add-upto-three-nums 1 2) +(add-upto-three-nums 1 2 3) +#_(add-upto-three-nums 1 2 3 4) ; will fail + + +;; Variable arity +;; - When we don't know in advance how many arguments we +;; will have to handle, but we want to handle them all. + +(defn add-any-numbers + [& nums] + (reduce + 0 nums)) + +(add-any-numbers) +(add-any-numbers 1) +(add-any-numbers 1 2) +(add-any-numbers 1 2 3 4 5) + + +;; Multiple _and_ Variable arities, combined +;; - Guess what + actually is inside? +;; +#_(clojure.repl/source +) ; evaluate, check the REPL +;; +;; See how + tries to implement each arity as a special case, +;; to compute results as optimally as possible? We can do +;; such things too, in functions we define. + +(+) +(+ 1) +(+ 1 2 3 4 5 6 7 8 9 0) + -;; Yes. +;; We can also use multiple arities to define sane fallbacks. + +;; EXERCISE +;; - Recall `lower-bound`, and `upper-bound` from ex03 +;; - Refactor these to support more than one arity. + +(def tolerances + "Define low/high bounds of planetary characteristics we care about." + {:co2 {:low 0.1, :high 5.0} + :gravity {:low 0.1, :high 2.0} + :surface-temp-deg-c {:low -125, :high 60}}) + +(defn lower-bound + [tolerance-key] + (get-in tolerances [tolerance-key :low])) + +(defn upper-bound + [tolerance-key] + (get-in tolerances [tolerance-key :high])) -;; We use destructuring: -;; - In `let` bindings, to cleanly reach into data -;; - In function arguments, to make the API clean and expressive +;; Fix `lower-bound-v2`, to make this expression evaluate +;; to true. Pay close attention to what should go where. +(defn lower-bound-v2 + "Look up the lower bound for the given tolerance key, in the + given map of `tolerances`. Use a globally-defined `tolerances` + map as a sane default if only tolerance-key is passed in." + ([tolerance-key] + (get-in tolerances + [tolerance-key :low])) + ([FIX1 FIX2] + 'FIX)) -;; Here are a couple of commonly-used ways to do it. +#_(= (lower-bound :co2) + (lower-bound-v2 :co2) + (lower-bound-v2 :co2 tolerances) + (lower-bound-v2 :co2 {:co2 {:low 0.1}})) +(comment + ;; BONUS EXERCISE + ;; Do the same for `upper-bound-v2` + #_(defn upper-bound-v2 + 'FIX + 'FIX) + + #_(= (upper-bound :co2) + (upper-bound-v2 :co2) + (upper-bound-v2 :co2 tolerances) + (upper-bound-v2 :co2 {:co2 {:low 0.1}})) + ) + + +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; A tiny bit of "De-structuring" +;; - For convenient access to items in collections. +;; +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(comment + ;; If we _shape_ our domain information as a data "structure", + ;; can we use our knowledge of the shape to pull it apart; + ;; i.e. "destructure" it? + + ;; Yes. + + ;; We use destructuring: + ;; - In `let` bindings, to cleanly reach into data + ;; - In function arguments, to make the API clean and expressive + ;; + ;; Here are a couple of commonly-used ways to do it. + ) + ;; "Positional" De-structuring ;; ;; - Pull apart sequential, ordered data structures like @@ -104,33 +213,17 @@ ;; - Relate it to our preferred way to model the world. ;; - What is the "world" here? ;; - What are we using to model/describe what property of what? +;; (Yes, that's three 'what's :-) ;; EXERCISE: ;; ;; - Use de-structuring to refactor the following functions -;; that we have copied over from ex03. +;; that we have copied over from ex03. ;; ;; - Develop a preliminary opinion about where and when -;; it might makes sense to de-structure, and where and when -;; it might not. - - -(def tolerances - "Define low/high bounds of planetary characteristics we care about." - {:co2 {:low 0.1, :high 5.0} - :gravity {:low 0.1, :high 2.0} - :surface-temp-deg-c {:low -125, :high 60}}) - - -(defn lower-bound - [tolerance-key] - (get-in tolerances [tolerance-key :low])) - - -(defn upper-bound - [tolerance-key] - (get-in tolerances [tolerance-key :high])) +;; it might makes sense to de-structure, and where and when +;; it might not. (defn atmosphere-present? @@ -172,7 +265,7 @@ ;; EXERCISE: ;; - Fix the body of the refactored function ;; - Carefully review the function APIs, and develop a preliminary -;; opinion whether the refactored version is better than the original. +;; opinion whether the refactored version is better than the original. (defn surface-temp-tolerable? @@ -212,6 +305,11 @@ ;; RECAP: ;; ;; - We model the world by composing data structures and then use -;; "de-structuring" to conveniently reach into those structures. +;; "de-structuring" to conveniently reach into those structures. +;; - We can design function apis to accept more than one arity, +;; and then define custom logic for each arity. ;; - When, where, and how much to de-structure is a matter of -;; taste; a design choice. There is no One True Way. +;; taste; a design choice. There is no One True Way. +;; - There are _many_ many ways of de-structuring. +;; Here's a really nice post detailing it: +;; cf. http://blog.jayfields.com/2010/07/clojure-destructuring.html From 735b35586fcd7fc361debaa0799f099314e1cd9e Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 7 Jan 2019 20:47:14 +0530 Subject: [PATCH 26/62] Refactor ex05; retain only immutability + FP and improve its discussion a bit. Get rid of lexical scope, as well as multi-arities and destructuring, as we have moved those earlier in the sequence. --- .../ex05_immutability_and_fp.clj | 312 ++---------------- 1 file changed, 24 insertions(+), 288 deletions(-) diff --git a/src/clojure_by_example/ex05_immutability_and_fp.clj b/src/clojure_by_example/ex05_immutability_and_fp.clj index 48c80dd..add82b5 100644 --- a/src/clojure_by_example/ex05_immutability_and_fp.clj +++ b/src/clojure_by_example/ex05_immutability_and_fp.clj @@ -1,6 +1,5 @@ (ns clojure-by-example.ex05-immutability-and-fp) - ;; Ex05: Lesson Goals ;; - This section is more conceptual, than exercise-oriented. ;; @@ -8,10 +7,8 @@ ;; in the final section (and in all our Clojure programs) ;; - All values are immutable by default (and we like it this way) ;; - What `def` is -;; - Lexical scope, and why you should avoid global defs +;; - Why you should avoid global defs ;; - What are "pure functions"? -;; - Convenient syntax for functions with multiple arities, -;; variable arities, and hash-maps or vectors as arguments. ;; ;; - Don't forget to evaluate all s-expressions that interest you, ;; and also feel free to write and play around with your own ones. @@ -141,7 +138,26 @@ other-pi ; what should this be? ;; - It's dangerous because re-defining a var alters it globally. ;; - Why so dangerous? ;; -;; Well, remember functions are values? +;; Well, compare the following. +;; - All three are _incorrect_, because pi is wrong. +;; - But only the third one is actually dangerous. + +(defn scale-by-pi-v1 + [n] + (let [pi 42] + (* pi n))) + +(defn scale-by-pi-v2 + [n] + (* weird-pi n)) + +#_(defn scale-by-pi-v3 + [n] + (def pi 42) + (* n pi)) + + +;; Also, remember functions are values? (fn [x] x) ; is a value (which shall remain anonymous) @@ -184,79 +200,6 @@ other-pi ; what should this be? ;; know what your are doing. - -;; Lexical Scope, and Global Vars - - -(def x 42) ; `x` is a global "var" - -(defn x+ - [y] - (+ x 1)) ; this `x` refers to the global `x` - -(x+ 1) ; will return 43 -(x+ 9) ; will still return 43 - - -(defn x++ - [x] ; this `x` is local to the scope of x++, - ; and will "shadow" the global `x` - (+ x 1)) - -(x++ 1) ; will return 2 -(x++ 9) ; will return 10 - - -(defn x+++ - [x] ; this `x` will shadow the global `x` - (let [x 10] ; but this `x` is local to the let, - ; and will shadow all "outer" x-es - (+ x 1))) - -(x+++ 1) ; will return 11 -(x+++ 9) ; will still return 11 - - -;; Lexical scope guarantees that the reference to a value will be -;; "enclosed" in the scope in which it is being used. - -;; This makes it very easy to reason about where a value originated. -;; - Start at the place of reference of the value. -;; - Then "walk" outwards, until you meet the very first let binding, -;; or arg-list, or def, where the value was bound. -;; - Now you know where the value came from. - - -;; EXERCISE: -;; -;; Reason about what is happening here: - -(let [x 3.141] - ((fn [x] x) x)) - - -;; How about this? What will happen here? - -((fn [x] x) x) - - -;; How about this? - -(let [x x] - ((fn [x] x) x)) - - -;; This? - -((fn [x] x) (let [x x] - x)) - -;; And finally, this? - -((let [x 3.141] - (fn [x] x)) x) - - ;; Lesson: ;; ;; - Clojure programmers use `def` _only_ to attach values to globally @@ -313,222 +256,15 @@ other-pi ; what should this be? ;; functions, or for other impure functions for that matter. - - -;; Convenient Syntax for functions -;; -;; - We use these conveniences for good effect in API design. - - -;; Multiple arities -;; - When we know for sure we have to handle some known numbers -;; of arguments. - -(defn add-upto-three-nums - ([] 0) ; identity of addition - ([x] x) - ([x y] (+ x y)) - ([x y z] (+ x y z))) - -(add-upto-three-nums) -(add-upto-three-nums 1) -(add-upto-three-nums 1 2) -(add-upto-three-nums 1 2 3) -#_(add-upto-three-nums 1 2 3 4) ; will fail - - -;; Variable arity -;; - When we don't know in advance how many arguments we -;; will have to handle, but we want to handle them all. - -(defn add-any-numbers - [& nums] - (reduce + 0 nums)) - -(add-any-numbers) -(add-any-numbers 1) -(add-any-numbers 1 2) -(add-any-numbers 1 2 3 4 5) - - -;; Multiple _and_ Variable arities, combined -;; - Guess what + actually is inside? -;; -#_(clojure.repl/source +) ; evaluate, check the REPL/LightTable console -;; -;; We can implement each arity as a special case, to compute results -;; as optimally as possible. - -(+) -(+ 1) -(+ 1 2 3 4 5 6 7 8 9 0) - - - -;; "De-structuring" -;; - For convenient access to items in collections. - - -;; Suppose a function expects a two-item sequence, we can... - -(defn destructure-tuple-in-strange-ways - [[a b]] ; expects a two-item vector - [[b] - [a] - ["abba" [a b b a]] - {:b b :a a} - (str "baa " b a a " black sheep.")]) - -(destructure-tuple-in-strange-ways [1 2]) - -;; It's like visually matching shapes to shapes. -;; - [1 2] ; structure 1 and 2 in a vector -;; | | -;; - [a b] ; name by position, and use each named value however we wish -;; -;; Said another way: -;; - When we put data into a data structure, we... structure the data. -;; - When we follow the structure of the data structure, but -;; reference each item by name, and "unpack" it for use, we have -;; just "de-structured" the data. - - -;; De-structuring works in `let` and functions: - -(let [[k v] [:a 42]] - {:a 42}) - -((fn [[k v]] {k v}) [:a 42]) - - -;; We can mix-and match de-structuring, for great good. -;; Compare: - -(reduce (fn [acc-map kv-pair] - (assoc acc-map - (first kv-pair) (second kv-pair))) - {:a 42} ; acc-map - {:b 0 :c 7 :d 10}) ; a hash-map is a collection of kv pairs - -(reduce (fn [acc-map [k v]] ; second arg is a tuple, so just destructure - (assoc acc-map k v)) - {:a 42} - {:b 0 :c 7 :d 10}) - - -;; Vectors are ordered collections, which we de-structure by _position_. - -;; Hash-maps are _unordered_ collections. -;; - BUT, they are keyed by named keys. -;; - We can exploit this "pattern" as follows: - -;; Compare this: -(let [make-message (fn [planet] - (str "Planet " (:pname planet) " has " - (:moons planet) " moons."))] - (make-message - {:pname "Mars" :moons 2})) - - -;; With this... -(let [make-message (fn [{:keys [pname moons]}] - (str "Planet " pname " has " - moons " moons."))] - (make-message - {:pname "Mars" :moons 2})) - - -;; And we can further... -(let [make-message (fn [{:keys [pname moons]}] - (str "Planet " pname " has " - (or moons 0) " moons."))] - (map make-message [{:pname "Earth" :moons 1} - {:pname "Mars" :moons 2} - {:pname "Moonless"}])) - - -;; We can also alias the whole hash-map: - -(defn add-message-1 - [{:keys [pname moons] - :as planet}] ; alias the hash-map as `planet` - (assoc planet - :message (str "Planet " pname " has " - (or moons 0) " moons."))) - -(map add-message-1 [{:pname "Earth" :moons 1} - {:pname "Mars" :moons 2} - {:pname "Moonless"}]) - - -;; Finally, we can specify default values directly in the destructuring: - -(defn add-message-2 - [{:keys [pname moons] - :or {moons 0} ; use 0, if :moons is absent - :as planet}] - (assoc planet - :message (str "Planet " pname " has " - moons " moons."))) - -(map add-message-2 [{:pname "Earth" :moons 1} - {:pname "Mars" :moons 2} - {:pname "Moonless"}]) - - -;; Further, we can exploit combinations of de-structuring -;; -;; - Suppose we have a hash map, keyed by planet names: -;; -{"Earth" {:moons 1} - "Mars" {:moons 2} - "Moonless" {}} -;; -;; - Recall: a hash-map is like a collection of key-value pairs/tuples -;; -;; - Now, we can exploit vector and map de-structuring, in combination: -;; - -(defn add-message-3 - [acc-map [pname {:keys [moons] - :or {moons 0} - :as pdata}]] - (let [msg (str "Planet " pname " has " moons " moons.")] - (assoc acc-map - pname (assoc pdata :message msg)))) - -(reduce add-message-3 - {} ; acc-map - {"Earth" {:moons 1} - "Mars" {:moons 2} - "Moonless" {} - "Nomoon" nil}) - - -;; There are _many_ many ways of de-structuring. -;; - Here's a really nice post detailing it: -;; cf. http://blog.jayfields.com/2010/07/clojure-destructuring.html - - - ;; RECAP: +;; ;; - Clojure values are immutable by default, and we prefer it that way ;; ;; - `def` is best used only to define names for truly global values. +;; Use `let` to bind local values, to get all the benefits of strict +;; lexical scope. ;; ;; - `defn` is just a wrapper over `def`, designed specifically to ;; define functions. ;; -;; - We exploit lexical scope to bind values as close as possible to the -;; point of use in code. This greatly improves our ability to reason -;; about our code. And it prevents an explosion of global `def`s. -;; ;; - Write pure functions as far as possible. -;; -;; - Conveniences like multi-arity and variable-arity functions, with -;; argument de-structuring, help us design better functional APIs. -;; -;; - We can mix-and match these facilities, for even more convenience. -;; -;; - Next, we will see how to "keep state at the boundary", and -;; keep the majority of our core logic purely functional. From de1143cad791d8905d906bfe844bc7bfa0007c26 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 7 Jan 2019 21:37:25 +0530 Subject: [PATCH 27/62] Fixup ex06 & make corresponding fixes in ex07 - remove stale references - ex04 is totally different, so don't reference anything from there - also make IDE-specific instructions generic (don't reference LightTable or IntelliJ etc.) - don't use clojure.walk to denormalize; instead use json/read's own api, which would be the more idiomatic way to kewordise keys - improve a few function names - tweak some indentation --- .../ex06_full_functional_firepower.clj | 52 +++++++++---------- src/clojure_by_example/ex07_boldly_go.clj | 37 +++++++------ 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/clojure_by_example/ex06_full_functional_firepower.clj b/src/clojure_by_example/ex06_full_functional_firepower.clj index 088590f..a6602dd 100644 --- a/src/clojure_by_example/ex06_full_functional_firepower.clj +++ b/src/clojure_by_example/ex06_full_functional_firepower.clj @@ -1,4 +1,6 @@ -(ns clojure-by-example.ex06-full-functional-firepower) +(ns clojure-by-example.ex06-full-functional-firepower + (:require [clojure.data.json :as json] + [clojure.java.io :as io])) ;; Ex06: Lesson Goals ;; - This is more of a code-reading section, designed to: @@ -144,11 +146,12 @@ ;; ---------------------- Input Boundary begins ------------------------ -(defn ingest-json-file +(defn ingest-json-file! [dir-path file-name] (let [file-path (str dir-path file-name)] - (with-open [reader (clojure.java.io/reader file-path)] - (clojure.data.json/read reader)))) + (with-open [reader (io/reader file-path)] + (json/read reader + :key-fn keyword)))) (defn gather-all-sensor-data! @@ -159,8 +162,8 @@ (let [ingest-sensor-data (fn [out-map [sensor-key sensor-file]] (assoc out-map - sensor-key (ingest-json-file data-dir - sensor-file)))] + sensor-key (ingest-json-file! data-dir + sensor-file)))] (reduce ingest-sensor-data {} ; out-map starts empty sensor-files-map))) @@ -200,26 +203,24 @@ atmosphere)) -(defn denormalise-planetary-data +(defn denormalise-planetary-data* "Given a hash-map of planetary data (keyed by planet names), return just the planetary data, with the planet's names added in. Also ensure all keys are keywordized, for convenient look-ups." [planets] (map (fn [[pname pdata]] - (let [keywordized-pdata (clojure.walk/keywordize-keys - pdata)] - (assoc keywordized-pdata - :name pname))) + (assoc pdata + :name (name pname))) planets)) -(defn denormalised-planetary-data +(defn denormalise-planetary-data "Given all sensor data, produce a collection of denormalized planetary data." [{:keys [planets atmosphere moons] :as all-sensor-data}] - ((comp denormalise-planetary-data + ((comp denormalise-planetary-data* (partial add-atmosphere-data atmosphere) (partial add-moon-data moons)) planets)) @@ -229,26 +230,26 @@ ;; First try this, to check packaged data... -#_(denormalised-planetary-data +#_(denormalise-planetary-data (gather-all-sensor-data! sensor-data-dir sensor-data-files)) ;; Does the result look familiar? -(defn write-out-json-file +(defn write-out-json-file! [dir-path file-name data] (let [file-path (str dir-path file-name)] - (with-open [writer (clojure.java.io/writer file-path)] - (clojure.data.json/write data writer)))) + (with-open [writer (io/writer file-path)] + (json/write data writer)))) (defn ingest-export-sensor-data! [data-dir source-data-files dest-data-file] - (write-out-json-file + (write-out-json-file! data-dir dest-data-file - (denormalised-planetary-data + (denormalise-planetary-data (gather-all-sensor-data! data-dir source-data-files)))) @@ -268,15 +269,10 @@ ;; The Planetary Data Scientists reveal their nefarious intentions: -#_(clojure-by-example.ex04-control-flow/colonize-habitable-planets! - (denormalised-planetary-data - (gather-all-sensor-data! sensor-data-dir - sensor-data-files))) -;; Note: -;; - For this to work, colonize-habitable-planets must be complete -;; - And you should have first evaluated it, to make it unable -;; - Which means, you have to first correctly solve ex04 - +;; (colonize-habitable-planets! ; deviously implemented by them, elsewhere +;; (denormalise-planetary-data +;; (gather-all-sensor-data! sensor-data-dir +;; sensor-data-files))) ;; RECAP: diff --git a/src/clojure_by_example/ex07_boldly_go.clj b/src/clojure_by_example/ex07_boldly_go.clj index e586f04..d8f9861 100644 --- a/src/clojure_by_example/ex07_boldly_go.clj +++ b/src/clojure_by_example/ex07_boldly_go.clj @@ -24,7 +24,7 @@ ;; lein new planet_coloniser ;; ;; -;; * Open the directory in LightTable and observe its structure. +;; * Open the directory in your IDE and observe its structure. ;; ;; ;; * Create a `utils` directory in which to put I/O utility functions. @@ -34,7 +34,7 @@ ;; - Terminal: ;; mkdir src/planet_coloniser/utils ;; -;; - Or, LightTable: +;; - Or, in your IDE: ;; - right-click on src/planet_coloniser, and create new folder ;; ;; @@ -42,7 +42,7 @@ ;; - `ingest.clj` and ;; - `export.clj` ;; -;; - Again, you can right-click on your `utils` dir in LightTable, +;; - Again, you can right-click on your `utils` dir in your IDE, ;; and create New File from the pop-up menu. ;; ;; @@ -53,7 +53,11 @@ ;; (ns planet-coloniser.utils.ingest) ;; ;; - Observe the dir name is planet_coloniser, but the ns -;; declaration has planet-coloniser. Why? +;; declaration has planet-coloniser. This is the convention: +;; - hyphens separate words in ns names +;; - dots separate directories and files in ns names +;; - underscores from dir or file names become hyphens in ns names +;; - and ns names are all lower case ;; ;; - Copy-paste the "input" function definitions from ex06, ;; below the ns declaration. @@ -66,7 +70,7 @@ ;; ;; - Inside `project.clj`, update :dependencies value to look like: ;; -;; :dependencies [[org.clojure/clojure "1.8.0" ; pre-existing +;; :dependencies [[org.clojure/clojure "1.10.0" ; latest as of 01 Jan 2019 ;; [org.clojure/data.json "0.2.6"]] ; add for `ingest` ;; ;; - Inside `injest.clj`, update the ns declaration to look like: @@ -112,28 +116,27 @@ ;; (:gen-class) ; add this, and the :require expression below: ;; (:require [planet-coloniser.sensor-processor :as sensproc] ;; [planet-coloniser.utils.ingest :as ingest] -;; [planet-coloniser.utils.export :as export] -;; [clojure.walk :as cwalk])) +;; [planet-coloniser.utils.export :as export])) ;; ;; - Copy `ingest-export-sensor-data!` from ex06 ;; ;; - Rename it to `-main`. ;; ;; - Now, make the body of `-main` look like the function below: -;; - notice prefixes to functions, like cwalk/, export/, ingest/ +;; - notice prefixes to functions, like export/, ingest/, sensproc/ ;; - Why do we do this? ;; ;; (defn -main ;; [data-dir source-data-files dest-data-file] -;; (let [source-data-files -;; (cwalk/keywordize-keys -;; (ingest/ingest-json-file data-dir -;; source-data-files))] -;; (export/write-out-json-file -;; data-dir -;; dest-data-file -;; (sensproc/denormalized-planetary-data -;; (ingest/gather-all-sensor-data! data-dir source-data-files))))) +;; (let [source-data-files (ingest/ingest-json-file! data-dir +;; source-data-files) +;; export-as-json (partial export/write-out-json-file! +;; data-dir +;; dest-data-file)] +;; (export-as-json +;; (sensproc/denormalise-planetary-data +;; (ingest/gather-all-sensor-data! data-dir +;; source-data-files))))) ;; ;; ;; * Let's bring in sensor data, for convenience: From 559aa811831c799653a92ff3f3c0f56da217efc6 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 7 Jan 2019 21:49:28 +0530 Subject: [PATCH 28/62] Ignore iml files generated by IntelliJ --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8203bfd..5b770c4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ pom.xml.asc .setup.sh profiles.clj .idea +*.iml From 19732403b7cca411925b0588d7dafadb75ad0537 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 7 Jan 2019 22:08:53 +0530 Subject: [PATCH 29/62] Reorder ex06 comment to un-break workshop_fmt.clj In the old position, workshop_fmt.clj would break for ex06 with this error: Execution error (UnsupportedOperationException) at rewrite_clj.node.comment.CommentNode/sexpr (comment.clj:6) whitespace.clj: 52 rewrite-clj.node.whitespace.NewlineNode/sexpr Weird! --- src/clojure_by_example/ex06_full_functional_firepower.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clojure_by_example/ex06_full_functional_firepower.clj b/src/clojure_by_example/ex06_full_functional_firepower.clj index a6602dd..3524172 100644 --- a/src/clojure_by_example/ex06_full_functional_firepower.clj +++ b/src/clojure_by_example/ex06_full_functional_firepower.clj @@ -117,8 +117,8 @@ ;; - Document the schema as in-line comments, for this dirty prototype. ;; (def sensor-data-files - {;; {"Planet Name":{"radius":}, ...} - :planets "planet_detector.json" + ;; {"Planet Name":{"radius":}, ...} + {:planets "planet_detector.json" ;; {"Planet Name":, ...} :moons "moon_detector.json" From ecca6722e5955974008c78eaa8f6e218bf95556c Mon Sep 17 00:00:00 2001 From: Sandilya Jandhyala Date: Tue, 8 Jan 2019 14:10:18 +0530 Subject: [PATCH 30/62] Fix minor README typos --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eae3364..dbc8fba 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ First, make sure you have Java 8. Notes: - If you have Java 9+, or Open JDK 9+ that should be OK too. - - The LightTable editor is known to break with Java 9. Use Java 8 instead. - We have not tested this project with Java 7. @@ -92,7 +91,7 @@ Set up an editor and figure out how to evaluate Clojure code with it. We support IntelliJ + Cursive IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. Avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! - Download and Install [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) - - Install and configure the Cursive plugin for IntelliJ by followint the [official Cursive user guide](https://cursive-ide.com/userguide/). + - Install and configure the Cursive plugin for IntelliJ by following the [official Cursive user guide](https://cursive-ide.com/userguide/). Once installed: @@ -159,6 +158,6 @@ Live long, and prosper. ## Copyright and License -Copyright © 2017-2018 [IN/Clojure](http://inclojure.org/). +Copyright © 2017-2018 [IN/Clojure](http://inclojure.org/). Distributed under the [MIT license](https://github.com/inclojure-org/clojure-by-example/blob/master/LICENSE). From 5181d3062a7ff8670938e8a83cb322167156b3b5 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 14 Jan 2019 11:50:08 +0530 Subject: [PATCH 31/62] Add README instructions to switch to ns & load file in IntelliJ, before evaluating expressions. Otherwise users will get runtime exceptions (symbols not found). --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dbc8fba..8f8a011 100644 --- a/README.md +++ b/README.md @@ -107,10 +107,13 @@ Once installed: - In the left pane, navigate down to the `project.clj` file, under the project's root folder. - Right click on `project.clj` and select the option that says "Run REPL for ..." - A right pane should open, with a REPL session. - - Now, open the `ex00..` file under the `src` folder, scroll down a little, till you see `(+ 1 2)`. + - Now, open the `ex00..` file under the `src` folder + - Use the menu under Tools > REPL to (a) Switch to the file's "namespace", and then (b) load the file into the REPL + - Scroll down a little, till you see `(+ 1 2)`. - Place your cursor after the closing parenthesis `)`, then right-click to open the context menu, and click on REPL > "Send '(+ 1 2)' to the REPL. - You should see '(+ 1 2)' appear in the REPL window, followed by `3`. This means you successfully evaluated an expression in the REPL. - - Now you may start from the top of ex00 and work through the material. + - Now you may start from the top of ex00 and work through the material in each "ex" file + - Important: For every exercise file, remember to first switch to the file's namespace, and load the file in the REPL (use the menu under Tools > REPL) Also keep the Cursive user guide handy, in case you need editor help, as you solve the workshop material. In particular, the [Paredit guide](https://cursive-ide.com/userguide/paredit.html) may be useful, if you stumble when editing Clojure code. From 7472e26aceb4a291cbe80c2e32b497533a17b714 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 14 Jan 2019 16:44:13 +0530 Subject: [PATCH 32/62] Add table of contents --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f8a011..8c6eabb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,19 @@ -# Clojure By Example + - [Introduction](#introduction) + - [Intended usage](#intended-usage) + - [Contributions](#contributions) + - [Workshop Goals](#workshop-goals) + - [Workshop Anti-Goals](#workshop-anti-goals) +- [Suggested learning mindset](#suggested-learning-mindset) +- [Setup Instructions](#setup-instructions) + - [Java 8](#java-8) + - [Leiningen](#leiningen) + - [Code Editor and Tooling](#code-editor-and-tooling) +- [Course Design Philosophy](#course-design-philosophy) +- [Credits](#credits) +- [Copyright and License](#copyright-and-license) + + +# Introduction What could one do with just a _little_ bit of Clojure? From ccd32cdebdec10bc58da47410da88d99ab9531bc Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sun, 24 Feb 2019 15:23:17 +0530 Subject: [PATCH 33/62] Fix annoying typos, improve some copy --- README.md | 4 ++-- src/clojure_by_example/ex00_introduction.clj | 2 +- src/clojure_by_example/ex03_data_and_functions.clj | 14 +++++++------- src/clojure_by_example/ex08_but_before_we_go.clj | 14 +++++--------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 8c6eabb..f5dca15 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ What could one do with just a _little_ bit of Clojure? - The `master` branch is heavily commented, for at-home use - A `solutions` branch will be available, as a companion to `master`. But don't peek at it in advance! - - Ignore the `workshop-code` branch. It is only for workshop use, - and subject to deletion/re-creation. + - You may see a `workshop-code` branch. Ignore it. It is meant only for + workshop use, and is subject to deletion/re-creation. ## Contributions - If you find bugs or errors, please send a PR (but please diff --git a/src/clojure_by_example/ex00_introduction.clj b/src/clojure_by_example/ex00_introduction.clj index b898657..9280b1d 100644 --- a/src/clojure_by_example/ex00_introduction.clj +++ b/src/clojure_by_example/ex00_introduction.clj @@ -228,7 +228,7 @@ same ; is defined in the current ns ;; - Notice, it's at the 1st position, and ;; - 2-4 are all arguments to defn ;; Further: - ;; - [2] is a Clojure symbol, `hello`, which will name the function + ;; - [2] is a Clojure symbol, `hie`, which will name the function ;; - [3] is a Clojure vector of two named arguments ;; - [4] is a Clojure s-expression, and is treated as the body of ;; the function definition diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index cb070f8..7f9333c 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -14,17 +14,17 @@ ;; in order to process a bunch of planets: ;; ;; - Standard Library (about 20 functions): -;; `def`, `defn`, `let` -- to define/name simple data and small functions -;; `get`, `get-in`, `assoc` -- to query and associate data -;; `map`, `filter`, `reduce` -- to operate on collections -;; `if`, `when`, `cond` -- to decide things -;; `not`, `and`, `empty?`, `<=`, `count` -- for logic and quantities -;; `comp`, `complement` -- to glue higher-order logic +;; `def`, `defn`, `fn`, `let` ; to create/name simple data and small functions +;; `get`, `get-in`, `assoc` ; to query and associate data +;; `map`, `filter`, `reduce` ; to operate on collections +;; `if`, `when`, `cond` ; to decide things +;; `not`, `and`, `empty?`, `<=`, `count` ; for logic and quantities +;; `comp`, `complement` ; to glue higher-order logic ;; ;; - Concepts: ;; - Compute only with pure functions: ;; - Build higher-order logic with higher order functions -;; - Lexical scope and function closures for +;; - Lexical scope and function closures to maximize modularity ;; - Collections as functions: ;; - Keywords as functions of hash-maps ;; - Well-defined Sets as predicates --- tests of set membership diff --git a/src/clojure_by_example/ex08_but_before_we_go.clj b/src/clojure_by_example/ex08_but_before_we_go.clj index 837c0c1..bd1df7b 100644 --- a/src/clojure_by_example/ex08_but_before_we_go.clj +++ b/src/clojure_by_example/ex08_but_before_we_go.clj @@ -2,16 +2,13 @@ ;; But before we boldly go, here are some resources to help us on our journey! -;; Communities: -;; Clojurians Slack: http://clojurians.net/ -;; IN/Clojure Open Relay: https://open.relay-chat.com/signup_user_complete/?id=inclojure -;; IN/Clojure Slack: https://join.slack.com/t/inclojure/shared_invite/enQtMjkzNDcyMjk1NDYwLWE4MzljNGRlZjcwZTRlYWFkODM3Mzc1NmU0M2Q3NjIxZmQ2NTYyZGU3MGVmZGJlMmFjYzBlNWM2Y2IwMjk0Y2Q -;; Clojure Subreddit: https://www.reddit.com/r/Clojure/ +;; Check out the official website: https://clojure.org -;; Books: -;; https://www.braveclojure.com/ -- free to read online -;; The Joy of Clojure -- http://www.joyofclojure.com/ +;; It has been accumulating many useful tutorials, guides, book references, +;; community resources, and information about companies using Clojure. +;; Also, here are a few things we really like and find useful to aid understanding +;; at various levels; from the philosophical to the here and now: ;; Talks/Philosphy: ;; https://www.youtube.com/watch?v=wASCH_gPnDw -- Inside Clojure with Brian Beckman and Rich Hickey @@ -29,7 +26,6 @@ ;; - Scroll down to the "Debugging Clojure" section: ;; https://aphyr.com/posts/319-clojure-from-the-ground-up-debugging - ;; Handy REPL utils: ;; ;; - clojure.repl/ From 7390239475c9dc5e114162efe0a281f48678fbb4 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Tue, 26 Feb 2019 01:27:41 +0530 Subject: [PATCH 34/62] Link to Kim Hirokuni's Clojure By Example to try and disambiguate the coincedentally named projects. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f5dca15..2210af1 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ What could one do with just a _little_ bit of Clojure? But don't peek at it in advance! - You may see a `workshop-code` branch. Ignore it. It is meant only for workshop use, and is subject to deletion/re-creation. + - Incidentally, if you landed here while searching for Kim Hirokuni's +"[Clojure By Example](https://kimh.github.io/clojure-by-example/)", well, follow the link! ## Contributions - If you find bugs or errors, please send a PR (but please From b49e4cda59cae66de035bf57681d99d939fa4370 Mon Sep 17 00:00:00 2001 From: Sandilya Jandhyala Date: Tue, 17 Dec 2019 10:31:25 +0530 Subject: [PATCH 35/62] Introduce a subtle and insidious bug to practice debugging --- src/clojure_by_example/ex03_data_and_functions.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index 7f9333c..bb4ab4c 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -228,7 +228,7 @@ p/target-planets (def fatal-conditions "A collection of functions that tell us about the fatality of planetary conditions." - [(complement atmosphere-present?) + [complement atmosphere-present? air-too-poisonous?]) From b185004302eaef4ce313861bdf15926f01f0e01e Mon Sep 17 00:00:00 2001 From: Sandilya Jandhyala Date: Tue, 17 Dec 2019 11:06:20 +0530 Subject: [PATCH 36/62] WIP: Write up debugging walkthrough --- .../ex03_data_and_functions.clj | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index bb4ab4c..b5a9015 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -333,3 +333,46 @@ p/target-planets #_(map assign-vessels p/target-planets) + +;; Something's not right...The Office of Interstellar Affairs tells us we're not assigning vessels correctly?! +;; We've only deployed probes and orbiters, and no other vessels?! + +;; We spent all this time and 300 lines of code to direct these vessels, and the orders aren't even correct! +;; We don't even see an error message! Clearly Clojure is the worst language ever made! + +;; ...OR IS IT? + +(comment + ;; It's time to learn how Clojure allows us to debug and understand our programs, using nothing more + ;; than the REPL and our wits. + + ;; Let's look at our results again, shall we? Are we really only deploying probes and orbiters? + + (map assign-vessels p/target-planets) + + ;; That's a bit hard to visually parse, how about this: + + (map :mission-vessels (map assign-vessels p/target-planets)) + + ;; The OIA is right! But why is this happening? + ;; Either our directive to fleet mapping is wrong, or our issued directives are wrong. + + starfleet-mission-configurations + + ;; The configurations look fine. What about the directives? + + (map :mission-directive (map assign-vessels p/target-planets)) + + ;; We're only probing and observing! Clearly issue-mission-directive is at fault. + ;; Let's take a look at its source code again. + + ;; Does this mean that there are no planets which our code considers habitable or colonisable? + + ;; EXERCISE: + ;; Check whether we have any habitable or colonisable planets according to the habitable? and colonisable? predicates. + + + + ;; + ) + From 80c96644b5a9d6e0b4bbff035da6cbffaecafdb5 Mon Sep 17 00:00:00 2001 From: Sandilya Jandhyala Date: Sun, 29 Dec 2019 08:52:25 +0530 Subject: [PATCH 37/62] First draft of debugging walkthrough --- .../ex03_data_and_functions.clj | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index b5a9015..d553924 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -4,11 +4,11 @@ ;; Ex03: LESSON GOALS -;; - Primarily, a code reading exercise ;; - Explore various bits and bobs of the solution interactively ;; using the live environment at your disposal ;; - Get some ideas of how to take just a handful of pieces, ;; and build sophisticated logic with them +;; - Debug any issues that might arise ;; - We use only the concepts and standard library functions ;; we've seen so far, to build purely functional logic ;; in order to process a bunch of planets: @@ -371,8 +371,48 @@ p/target-planets ;; EXERCISE: ;; Check whether we have any habitable or colonisable planets according to the habitable? and colonisable? predicates. + ;; Apparently we don't! + ;; At the very least, the planet Earth should be both habitable and colonisable. + ;; At least now we know that habitable? and colonisable? are problematic. But why? Let's look at their implementation. + ;; We'll narrow in on habitable? for the time being, and worry about colonisable? later. + + ;; A planet is habitable iff: + ;; 1. It has an atmosphere + ;; 2. The air is not too poisonous + ;; 3. The carbon dioxide, gravity and temperature levels are all tolerable + + ;; The following issues are possible: + ;; 1. planet-meets-any-one-condition? is broken. + ;; 2. planet-meets-no-condition? is broken. + ;; 3. minimal-good-conditions is broken. + ;; 4. fatal-conditions is broken. + ;; 5. Any or all of the above. + + ;; EXERCISE: Check if planet-meets-any-one-condition? works correctly. + ;; planet-meets-any-one-condition? accepts predicates as a parameter, and doesn't care about the predicates + ;; themselves. Because of this, we can simplify our debugging by using simple and obvious predicates, + ;; rather than using the predicates in the production code. + + ;; EXERCISE: Check if planet-meets-no-condition? works correctly. + + ;; If none of those work, clearly there's something wrong with our conditions themselves. + + ;; EXERCISE: Diagnose and fix the broken conditions. + + ;; The Clojure REPL is a powerful debugging tool that supersedes more traditional step-through debuggers + ;; in many ways. + ;; You can: + ;; 1. Test individual functions or constants to check if they're correct. + ;; 2. Redefine a function to add tracing such as print statements, or other forms of instrumentation. + ;; 3. Capture intermediate values such as function arguments or let bindings, and inspect them in the REPL + ;; after the fact. + ;; 4. Fix the problem and verify that it works immediately. + ;; 5. Do all of the above either locally, or while connected to a remote server running in a staging or + ;; production environment. + + ;; We strongly recommend going through https://clojure.org/guides/repl/enhancing_your_repl_workflow#debugging-tools-and-techniques + ;; for more tips, tricks and resources related to debugging. The entire REPL guide is useful, but the section about debugging + ;; is particularly pertinent. - - ;; ) From 7da85101212a322463e4c29a7af7049b6f06830f Mon Sep 17 00:00:00 2001 From: Sandilya Jandhyala Date: Sun, 29 Dec 2019 08:54:59 +0530 Subject: [PATCH 38/62] Add final testing step in debugging walkthrough --- src/clojure_by_example/ex03_data_and_functions.clj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index d553924..ee0a9b5 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -399,6 +399,10 @@ p/target-planets ;; EXERCISE: Diagnose and fix the broken conditions. + ;; Does everything work now? + + (map assign-vessels p/target-planets) + ;; The Clojure REPL is a powerful debugging tool that supersedes more traditional step-through debuggers ;; in many ways. ;; You can: From 9d6773aa174fd15c06075e511633e7604a6d5738 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sat, 8 Feb 2020 12:54:44 +0530 Subject: [PATCH 39/62] Add local m2 to let us zip & share the repo standalone With a project-local .m2, we can share workshop deps cleanly without having to worry about leaking our own ~/.m2 which may have proprietary or "production" jars. With this change we can simply execute lein deps, and zip the entire project so it becomes shareable standalone. Not sure if this will work for Windows users, but at least they will have the deps, and can copy them into whatever local m2 location works for them. --- .gitignore | 1 + project.clj | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5b770c4..27613b2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ pom.xml.asc .setup.sh profiles.clj .idea +.m2-for-inclojure *.iml diff --git a/project.clj b/project.clj index cf35f51..2c5b89c 100644 --- a/project.clj +++ b/project.clj @@ -5,6 +5,7 @@ :url "https://opensource.org/licenses/MIT"} ;; Requirements: Java 8 or higher (recommended: Java 8 or Java 11) :dependencies [[org.clojure/clojure "1.10.0"]] + :local-repo ".m2-for-inclojure" :profiles {:dev {:dependencies [[org.clojure/data.json "0.2.6"] [enlive "1.1.6"] [rewrite-clj "0.6.1"]]}}) From 5e560ec4ae9872ec9dc6b3de106880f055c45cb4 Mon Sep 17 00:00:00 2001 From: Sandilya Jandhyala Date: Tue, 11 Feb 2020 10:12:07 +0530 Subject: [PATCH 40/62] Make Java and Leiningen setup optional --- README.md | 72 +++++++++++++++++++------------------------------------ 1 file changed, 25 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 2210af1..a30eaa7 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,6 @@ - [Workshop Anti-Goals](#workshop-anti-goals) - [Suggested learning mindset](#suggested-learning-mindset) - [Setup Instructions](#setup-instructions) - - [Java 8](#java-8) - - [Leiningen](#leiningen) - - [Code Editor and Tooling](#code-editor-and-tooling) - [Course Design Philosophy](#course-design-philosophy) - [Credits](#credits) - [Copyright and License](#copyright-and-license) @@ -64,46 +61,7 @@ It's a liiitle bit of work. But not too bad. Just do the following one by one, and you should be fine. -## Java 8 - -You will need Java to work with this Clojure workshop content. - -First, make sure you have Java 8. - - - Run `java -version` in your terminal. - - If Java is not installed, please [download and install Java 8 from here](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html). - - Once you are done, `java -version` should show you a Java 1.8.x version. - -Notes: - - - If you have Java 9+, or Open JDK 9+ that should be OK too. - - We have not tested this project with Java 7. - - -## Leiningen - -Follow [Leiningen setup instructions here](https://leiningen.org/). - -### Fire up a REPL - - - Clone this project - - Open your terminal, and do the following. - - `cd` into this project's root directory - - Use `lein repl` command to start a REPL with Leiningen. - - Wait for it... the REPL will start and print out a message with some - useful information - - Locate the `port` and `host` information in the message. We will need this information soon. - -Note: - - - [Boot](http://boot-clj.com/) should be fine too, but we have not tested this project with it. - - -## Code Editor and Tooling - -Set up an editor and figure out how to evaluate Clojure code with it. - -### IntelliJ + Cursive IDE +## IntelliJ + Cursive IDE We support IntelliJ + Cursive IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. Avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! @@ -121,8 +79,7 @@ Once installed: - Again, click Next in the screen that says "Select Leiningen projects to import" - And again, click Next in the "Please select project SDK" screen (ensure you select JDK version 1.8 or higher) - Click "Finish", and wait for IntelliJ to set up the project - - In the left pane, navigate down to the `project.clj` file, under the project's root folder. - - Right click on `project.clj` and select the option that says "Run REPL for ..." + - Under `Run...` click on `Run...` and then select `REPL for clojure-by-example` (or whatever your project name happens to be). - A right pane should open, with a REPL session. - Now, open the `ex00..` file under the `src` folder - Use the menu under Tools > REPL to (a) Switch to the file's "namespace", and then (b) load the file into the REPL @@ -134,16 +91,37 @@ Once installed: Also keep the Cursive user guide handy, in case you need editor help, as you solve the workshop material. In particular, the [Paredit guide](https://cursive-ide.com/userguide/paredit.html) may be useful, if you stumble when editing Clojure code. +## (Optional) Java and Leiningen +Being a JVM hosted language, Clojure requires Java to run. For the workshop, we also use a Clojure build tool called Leiningen. +If you're using IntelliJ + Cursive for the workshop, you won't need to install Java or Leiningen separately, since Intellij will come with a JDK and Cursive will download Leiningen when you import the project. +So just follow the IntelliJ + Cursive setup guide and you'll be good to go for the workshop. + +If you're working on a production project however, it's useful to have both Java and Leiningen separately installed. + +### Java + + - Run `java -version` in your terminal. + - If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). Any version should do. + - Once you are done, `java -version` should show you a Java version. + +Notes: + - We have not tested this project with Java 7. + + +### Leiningen + +Follow [Leiningen setup instructions here](https://leiningen.org/). -### Alternative Starter Kits: +## Alternative Starter Kits: If you can't use IntelliJ for some reason, you may try one of these. Although we haven't tested with these setups, the workshop material should work fine. +You'll also have to install Leiningen and Java separately. - A [snazzy setup with Atom](https://medium.com/@jacekschae/slick-clojure-editor-setup-with-atom-a3c1b528b722). - Brave Clojure walks you through [a basic Emacs setup for learning Clojure](https://www.braveclojure.com/basic-emacs/). -### Your favourite editor: +## Your favourite editor: You may find instructions for your favourite editor at one of these pages. But there are only so many choices. Ultimately, you must pick your poison and run with it: From a5c91ae6356024e1a28e15a4cfe35275b1e56b56 Mon Sep 17 00:00:00 2001 From: mihil Date: Wed, 8 Jan 2020 20:41:09 +0530 Subject: [PATCH 41/62] Add 4clojure problems before and after exercises - These exercises introduce concepts and accompanying code - I personally found a combination of these exercises and 4clojure to be quite useful in my journey of learning clojure - Suppose the exercise introduces the concept of reduce, after getting acquanited to the same, if someone also solves the corresponding 4clojure problems, the concept gets registered in the mind and gives confidence - I've tried to correlate these exercises with their corresponding 4clojure problems --- src/clojure_by_example/ex00_introduction.clj | 6 ++++++ src/clojure_by_example/ex01_fundamentally_functional.clj | 2 ++ src/clojure_by_example/ex02_domain_as_data.clj | 3 +++ src/clojure_by_example/ex03_data_and_functions.clj | 3 +++ src/clojure_by_example/ex04_api_design.clj | 3 +++ src/clojure_by_example/ex05_immutability_and_fp.clj | 1 + 6 files changed, 18 insertions(+) diff --git a/src/clojure_by_example/ex00_introduction.clj b/src/clojure_by_example/ex00_introduction.clj index 9280b1d..95138fe 100644 --- a/src/clojure_by_example/ex00_introduction.clj +++ b/src/clojure_by_example/ex00_introduction.clj @@ -3,6 +3,8 @@ ;; IMPORTANT: ;; - The README file explains why this project exists. +;; - Before reading this, try solving problems 1, 2, 3, +;; 4, 5, 7, 8, 9, 11, 12, 13 and 47 on 4clojure.com ;; - Begin in this "ex00..." file, and work through it step by step. ;; - Once you are done with "ex00...", open the next file and repeat. ;; - Keep going this way, until you have worked through all the files. @@ -247,3 +249,7 @@ same ; is defined in the current ns ;; ;; - All opening braces or parentheses must be matched by closing ;; braces or parentheses, to create legal Clojure expressions. +;; +;; - With this knowledge, try solving problems 6, 10, 16, 126, 161, 162 +;; on 4clojure.com + diff --git a/src/clojure_by_example/ex01_fundamentally_functional.clj b/src/clojure_by_example/ex01_fundamentally_functional.clj index c8a7d6d..a7c674b 100644 --- a/src/clojure_by_example/ex01_fundamentally_functional.clj +++ b/src/clojure_by_example/ex01_fundamentally_functional.clj @@ -461,3 +461,5 @@ reduce ;; - Learn to use lexical scope and function closures effectively. ;; - Learn to define small "single purpose" functions, such that ;; you can compose them together to produce higher order logic. +;; - Now try solving problems 14, 15, 19, 20, 48, 45 on +;; on 4clojure.com diff --git a/src/clojure_by_example/ex02_domain_as_data.clj b/src/clojure_by_example/ex02_domain_as_data.clj index 89a8da6..6a57401 100644 --- a/src/clojure_by_example/ex02_domain_as_data.clj +++ b/src/clojure_by_example/ex02_domain_as_data.clj @@ -236,3 +236,6 @@ ;; Only limited by your imagination! + +;; Now try solving problems 17, 18, 57, 71 +;; 134, 27, 26, 39 on 4clojure.com diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index ee0a9b5..6198e7c 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -334,6 +334,7 @@ p/target-planets #_(map assign-vessels p/target-planets) + ;; Something's not right...The Office of Interstellar Affairs tells us we're not assigning vessels correctly?! ;; We've only deployed probes and orbiters, and no other vessels?! @@ -420,3 +421,5 @@ p/target-planets ) +;; You've come a long way. Kudos! Try solving 37, 64, 72, 21, 24, 25, 38, 29, 42, +;; 31, 81, 107, 88, 157, 50, 46, 65 on 4clojure.com diff --git a/src/clojure_by_example/ex04_api_design.clj b/src/clojure_by_example/ex04_api_design.clj index 8401b75..0dfb319 100644 --- a/src/clojure_by_example/ex04_api_design.clj +++ b/src/clojure_by_example/ex04_api_design.clj @@ -313,3 +313,6 @@ ;; - There are _many_ many ways of de-structuring. ;; Here's a really nice post detailing it: ;; cf. http://blog.jayfields.com/2010/07/clojure-destructuring.html +;; - Try solving 35, 36, 68, 145, 52, 156, 22, 23, 32, 30, 34, 28, 33, 40, +;; 83, 61, 99, 120, 56, 55, 43, 67, 74, 80, 69, 75 on 4clojure.com + diff --git a/src/clojure_by_example/ex05_immutability_and_fp.clj b/src/clojure_by_example/ex05_immutability_and_fp.clj index add82b5..1cf8a1e 100644 --- a/src/clojure_by_example/ex05_immutability_and_fp.clj +++ b/src/clojure_by_example/ex05_immutability_and_fp.clj @@ -268,3 +268,4 @@ other-pi ; what should this be? ;; define functions. ;; ;; - Write pure functions as far as possible. +;; - Try solving questions 51, 77, 60, 102, 86, 115 on 4clojure.com From 8d4d6ce605a109da2031f13c3d590ee87b14c8cb Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Tue, 11 Feb 2020 12:59:00 +0530 Subject: [PATCH 42/62] Revise recap copy to pose 4clojure probs @mihil added and for consistency of location of recap (esp. ex03 copy about REPL based workflow skills). --- src/clojure_by_example/ex00_introduction.clj | 14 +++-- .../ex01_fundamentally_functional.clj | 14 ++++- .../ex02_domain_as_data.clj | 14 ++++- .../ex03_data_and_functions.clj | 57 ++++++++++++------- src/clojure_by_example/ex04_api_design.clj | 16 +++++- .../ex05_immutability_and_fp.clj | 12 +++- 6 files changed, 95 insertions(+), 32 deletions(-) diff --git a/src/clojure_by_example/ex00_introduction.clj b/src/clojure_by_example/ex00_introduction.clj index 95138fe..6319dcf 100644 --- a/src/clojure_by_example/ex00_introduction.clj +++ b/src/clojure_by_example/ex00_introduction.clj @@ -3,12 +3,11 @@ ;; IMPORTANT: ;; - The README file explains why this project exists. -;; - Before reading this, try solving problems 1, 2, 3, -;; 4, 5, 7, 8, 9, 11, 12, 13 and 47 on 4clojure.com ;; - Begin in this "ex00..." file, and work through it step by step. ;; - Once you are done with "ex00...", open the next file and repeat. ;; - Keep going this way, until you have worked through all the files. - +;; - Once done with a file, read the recap and try any additional +;; problem sets listed there, to get more practice. ;; EX00: LESSON GOAL: ;; - Drill some Clojure basics, required for the sections @@ -249,7 +248,10 @@ same ; is defined in the current ns ;; ;; - All opening braces or parentheses must be matched by closing ;; braces or parentheses, to create legal Clojure expressions. -;; -;; - With this knowledge, try solving problems 6, 10, 16, 126, 161, 162 -;; on 4clojure.com +;; 4clojure Drills: Problems you could try now. +;; +;; - With the knowledge you have so far, you can try solving these +;; problems at 4clojure.com: 1 to 13, and 16, 47, 126, 161, and 162 +;; e.g. http://www.4clojure.com/problem/1 +;; e.g. http://www.4clojure.com/problem/16 diff --git a/src/clojure_by_example/ex01_fundamentally_functional.clj b/src/clojure_by_example/ex01_fundamentally_functional.clj index a7c674b..3660445 100644 --- a/src/clojure_by_example/ex01_fundamentally_functional.clj +++ b/src/clojure_by_example/ex01_fundamentally_functional.clj @@ -461,5 +461,15 @@ reduce ;; - Learn to use lexical scope and function closures effectively. ;; - Learn to define small "single purpose" functions, such that ;; you can compose them together to produce higher order logic. -;; - Now try solving problems 14, 15, 19, 20, 48, 45 on -;; on 4clojure.com + +;; +;; 4clojure Drills: Problems you could try now. +;; +;; - #protip: Write the solutions as proper named functions in your code base, +;; without code-golfing or hacks. Then translate to anonymous function form +;; that 4clojure requires. +;; +(comment + (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + problem-no)) + [14, 15, 19, 20, 48, 45])) diff --git a/src/clojure_by_example/ex02_domain_as_data.clj b/src/clojure_by_example/ex02_domain_as_data.clj index 6a57401..e760ade 100644 --- a/src/clojure_by_example/ex02_domain_as_data.clj +++ b/src/clojure_by_example/ex02_domain_as_data.clj @@ -237,5 +237,15 @@ ;; Only limited by your imagination! -;; Now try solving problems 17, 18, 57, 71 -;; 134, 27, 26, 39 on 4clojure.com +;; +;; 4clojure Drills: Problems you could try now. +;; +;; - #protip: Write the solutions as proper named functions in your code base, +;; without code-golfing or hacks. Then translate to anonymous function form +;; that 4clojure requires. +;; +(comment + (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + problem-no)) + [17, 18, 57, 71 + 134, 27, 26, 39])) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index 6198e7c..5be05e8 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -334,7 +334,6 @@ p/target-planets #_(map assign-vessels p/target-planets) - ;; Something's not right...The Office of Interstellar Affairs tells us we're not assigning vessels correctly?! ;; We've only deployed probes and orbiters, and no other vessels?! @@ -403,23 +402,43 @@ p/target-planets ;; Does everything work now? (map assign-vessels p/target-planets) - - ;; The Clojure REPL is a powerful debugging tool that supersedes more traditional step-through debuggers - ;; in many ways. - ;; You can: - ;; 1. Test individual functions or constants to check if they're correct. - ;; 2. Redefine a function to add tracing such as print statements, or other forms of instrumentation. - ;; 3. Capture intermediate values such as function arguments or let bindings, and inspect them in the REPL - ;; after the fact. - ;; 4. Fix the problem and verify that it works immediately. - ;; 5. Do all of the above either locally, or while connected to a remote server running in a staging or - ;; production environment. - - ;; We strongly recommend going through https://clojure.org/guides/repl/enhancing_your_repl_workflow#debugging-tools-and-techniques - ;; for more tips, tricks and resources related to debugging. The entire REPL guide is useful, but the section about debugging - ;; is particularly pertinent. - ) -;; You've come a long way. Kudos! Try solving 37, 64, 72, 21, 24, 25, 38, 29, 42, -;; 31, 81, 107, 88, 157, 50, 46, 65 on 4clojure.com +;; +;; RECAP +;; - Hopefully, you now have a better handle on the various aspects +;; of working with Clojure, listed in the exercise goals; viz. +;; - Reading: How to explore an unfamiliar Clojure code-base _interactively_? +;; - "Primitives": How to get a lot done with just 20-odd core functions? +;; - Concepts: What helps us model our domains and compose functional logic? +;; - Workflow: How to apply the scientific method to development and debugging? +;; +;; - REPL all the things! +;; Especially understand how the Clojure REPL is a powerful debugging tool +;; that supersedes more traditional step-through debuggers in many ways. +;; +;; You can: +;; 1. Test individual functions or constants to check if they're correct. +;; 2. Redefine a function to add tracing such as print statements, or other forms of instrumentation. +;; 3. Capture intermediate values such as function arguments or let bindings, and inspect them in the REPL +;; after the fact. +;; 4. Fix the problem and verify that it works immediately. +;; 5. Do all of the above either locally, or while connected to a remote server running in a staging or +;; production environment. +;; +;; We strongly recommend going through https://clojure.org/guides/repl/enhancing_your_repl_workflow#debugging-tools-and-techniques +;; for more tips, tricks and resources related to debugging. The entire REPL guide is useful, but the section about debugging +;; is particularly pertinent. + +;; +;; 4clojure Drills: Problems you could try now. +;; +;; - #protip: Write the solutions as proper named functions in your code base, +;; without code-golfing or hacks. Then translate to anonymous function form +;; that 4clojure requires. +(comment + (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + problem-no)) + [37, 64, 72, 21, 24, 25, + 38, 29, 42, 31, 81, 107, + 88, 157, 50, 46, 65])) diff --git a/src/clojure_by_example/ex04_api_design.clj b/src/clojure_by_example/ex04_api_design.clj index 0dfb319..6050e91 100644 --- a/src/clojure_by_example/ex04_api_design.clj +++ b/src/clojure_by_example/ex04_api_design.clj @@ -313,6 +313,18 @@ ;; - There are _many_ many ways of de-structuring. ;; Here's a really nice post detailing it: ;; cf. http://blog.jayfields.com/2010/07/clojure-destructuring.html -;; - Try solving 35, 36, 68, 145, 52, 156, 22, 23, 32, 30, 34, 28, 33, 40, -;; 83, 61, 99, 120, 56, 55, 43, 67, 74, 80, 69, 75 on 4clojure.com +;; 4clojure Drills: Problems you could try now. +;; +;; - #protip: Write the solutions as proper named functions in your code base, +;; without code-golfing or hacks. Then translate to anonymous function form +;; that 4clojure requires. +;; +(comment + (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + problem-no)) + [35, 36, 68, 145, 52, + 156, 22, 23, 32, 30, + 34, 28, 33, 40, 83, + 61, 99, 120, 56, 55, + 43, 67, 74, 80, 69, 75])) diff --git a/src/clojure_by_example/ex05_immutability_and_fp.clj b/src/clojure_by_example/ex05_immutability_and_fp.clj index 1cf8a1e..4e31e54 100644 --- a/src/clojure_by_example/ex05_immutability_and_fp.clj +++ b/src/clojure_by_example/ex05_immutability_and_fp.clj @@ -268,4 +268,14 @@ other-pi ; what should this be? ;; define functions. ;; ;; - Write pure functions as far as possible. -;; - Try solving questions 51, 77, 60, 102, 86, 115 on 4clojure.com + +;; 4clojure Drills: Problems you could try now. +;; +;; - #protip: Write the solutions as proper named functions in your code base, +;; without code-golfing or hacks. Then translate to anonymous function form +;; that 4clojure requires. +;; +(comment + (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + problem-no)) + [51, 77, 60, 102, 86, 115])) From b8cbd59054053b59bcb52b32e1752bfdd080dce3 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Tue, 11 Feb 2020 13:31:31 +0530 Subject: [PATCH 43/62] Fix broken ns declaration in ex07 --- src/clojure_by_example/ex07_boldly_go.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clojure_by_example/ex07_boldly_go.clj b/src/clojure_by_example/ex07_boldly_go.clj index d8f9861..93a29ae 100644 --- a/src/clojure_by_example/ex07_boldly_go.clj +++ b/src/clojure_by_example/ex07_boldly_go.clj @@ -1,4 +1,4 @@ -(ns clojure-by-example.utils.ex07-boldly-go) +(ns clojure-by-example.ex07-boldly-go) ;; EX07: Lesson Goals From 395e32d0c7b418d93cba4d93cd82271668bb8b72 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Tue, 11 Feb 2020 13:41:42 +0530 Subject: [PATCH 44/62] Fix how Hirokuni Kim's name appears with reference to his website and github https://kimh.github.io/about/ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a30eaa7..18472d7 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ What could one do with just a _little_ bit of Clojure? But don't peek at it in advance! - You may see a `workshop-code` branch. Ignore it. It is meant only for workshop use, and is subject to deletion/re-creation. - - Incidentally, if you landed here while searching for Kim Hirokuni's + - Incidentally, if you landed here while searching for Hirokuni Kim's "[Clojure By Example](https://kimh.github.io/clojure-by-example/)", well, follow the link! ## Contributions From 9a4c19342f9e03bbcd6e143e0bef9223e01dfff4 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 24 Feb 2020 17:56:03 -0500 Subject: [PATCH 45/62] ex01 clarify why map returns things in round parens I've noticed several people are getting tripped up by this, because it's visually very noticeable. Makes sense to just write it here in-line, instead of pointing it out in class only. --- .../ex01_fundamentally_functional.clj | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/clojure_by_example/ex01_fundamentally_functional.clj b/src/clojure_by_example/ex01_fundamentally_functional.clj index 3660445..a4a8423 100644 --- a/src/clojure_by_example/ex01_fundamentally_functional.clj +++ b/src/clojure_by_example/ex01_fundamentally_functional.clj @@ -377,6 +377,25 @@ map ;; inc inc inc ; via `inc` ;; | | | | | | ;; (2 3 4 5 6 7) ; to each item of the output coll +;; +;; Note: you may wonder why the result of map inc on [1 2 3 4], which is +;; square-bracketed results in an answer that's wrapped in parens (2 3 4 5). +;; +;; The short answer is: Ignore this pesky detail. +;; Think in terms of "sequence in, sequence out", instead of "this 'type' of +;; sequence in, and the same 'type' of sequence out". +;; +;; The more confusing answer is: 'map' returns a "lazy" sequence, which the REPL +;; _prints_ out visually, with round parens. 'filter' (below) does the same too. +;; +;; Usually we don't care if we have a vector or a list or a "lazy" sequence. +;; What we do care is what the sequence contains, and that the thing remains +;; sequential before/after. It only starts mattering when we definitely want +;; a particular sequence type for the very specific performance guarantees +;; that it provides. +;; +;; But really, you'll do better if you just ignore what this actually means +;; and/or the consequences of the distinction for now. filter From 3726e86714a580d89f51b5190db5ad1a06427ddd Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Tue, 2 Jun 2020 23:20:24 +0530 Subject: [PATCH 46/62] Clarify a previously implicit goal of the workshop which is to set up the learner with a proper local dev env/workflow for future success. A discussion in issue #7 regarding possible interactive/online format of the workshop material led to this clarification. There are other ancillary reasons why it is the way it is, discussed in the issue, and x-posted below for the permanent record: cf. https://github.com/adityaathalye/clojure-by-example/issues/7 dijonkitchen commented 27 days ago Maybe consider using https://github.com/viebel/klipse for faster feedback right on the same page instead of repl.it or static, pre-formatted text? Might help let people play around with the code and see how it works. adityaathalye commented yesterday Thank you for the suggestion. There are a few considerations for the choice of development environment: 1. This workshop targets practicing programmers, and not complete beginners. Our opinion is that getting started with standard developer tooling is part of the learning. We don't want to recommend Emacs (most of us use Emacs), because tha't too alien for most people. So far, Intellij + Cursive has been a rock-solid, if heavy to set up, option. 2. High quality developer tooling is crucial for a good learning experience --- static checks, linting, paredit/parinfer support, super-fast feedback etc. help immensely. The standard developer tooling sees heavy investment in imporving these creature comforts. The workshop participants also expect tools of the quality they are accustomed to in their normal day to day work. 3. Avoid Internet connectivity when teaching in-person --- this is a failure mode / source of degraded experience we wish to avoid when time is of the essence. We are able to avoid it, with a combination of project-local .m2, which we can share via USB stick, and previously configured local dev setup. 4. Set up for future success. We hope programmers would want to keep going after the workshop, and start hobby projects. Having a local dev environment already set up removes the friction of having to switch tools / interaction models. 5. Maintainig support for multiple dev environments is overhead we can't take. Currently, we test the flow of the workshop on (Mac, Linux, Windows) x (1 IDE) x (1 local JVM). We don't have the bandwidth to ensure the learning experience is reliable on more platforms. If we choose an online dev tool, we also have to worry about browser compatibility issues and the necessarily sandboxed / opaque runtime of the online service. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 18472d7..d059904 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ What could one do with just a _little_ bit of Clojure? - Learn how Clojurists usually think with Clojure to solve problems. - See how it's not so hard to do surprisingly powerful things with a mere handful of "primitive" functions, data structures, and ideas. + - Get you started with a good development setup and workflow that will + serve you well if (when) you continue to program with Clojure, as a + hobby, or at work! ## Workshop Anti-Goals - Try to explain Functional Programming theory or Clojure's innards. From 5be14f3be0c486a1c1d252d1e018e033789649e0 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sun, 14 Mar 2021 16:28:00 +0530 Subject: [PATCH 47/62] Fix stale instructions in Ex07. Thanks @Grazfather. ref: https://github.com/adityaathalye/clojure-by-example/issues/8 The instructions went stale after the refactoring of Ex06, as of this SHA: de1143cad791d8905d906bfe844bc7bfa0007c26 --- src/clojure_by_example/ex07_boldly_go.clj | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/clojure_by_example/ex07_boldly_go.clj b/src/clojure_by_example/ex07_boldly_go.clj index 93a29ae..8e7caf1 100644 --- a/src/clojure_by_example/ex07_boldly_go.clj +++ b/src/clojure_by_example/ex07_boldly_go.clj @@ -76,7 +76,8 @@ ;; - Inside `injest.clj`, update the ns declaration to look like: ;; ;; (ns planet-coloniser.utils.ingest -;; (:require [clojure.data.json])) +;; (:require [clojure.data.json :as json] +;; [clojure.java.io :as io])) ;; ;; ;; * Update `export.clj`. Open the file and: @@ -89,8 +90,9 @@ ;; ;; - Also ensure, the ns form looks like this: ;; -;; (ns planet-coloniser.utils.export -;; (:require [clojure.data.json])) ; we use this in export too +;; (ns planet-coloniser.utils.ingest +;; (:require [clojure.data.json :as json] +;; [clojure.java.io :as io])) ;; ;; ;; * Create `sensor_processor.clj`, for our core "pure" logic: From 4b8ca16ef887c4907efef566bfcf9ff4486d8474 Mon Sep 17 00:00:00 2001 From: Ayush Sachdeva Date: Wed, 26 May 2021 13:27:14 +0530 Subject: [PATCH 48/62] Fix dead links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d059904..30f2f98 100644 --- a/README.md +++ b/README.md @@ -128,9 +128,9 @@ You'll also have to install Leiningen and Java separately. You may find instructions for your favourite editor at one of these pages. But there are only so many choices. Ultimately, you must pick your poison and run with it: - - ["IDEs and Editors" at dev.clojure.org](https://dev.clojure.org/display/doc/IDEs+and+Editors) + - ["Clojure Tools" at clojure.org](https://clojure.org/community/tools) - ["Essentials" at clojure-doc.org](http://clojure-doc.org/articles/content.html#essentials) - - [Christopher Bui says...](https://cb.codes/what-editor-ide-to-use-for-clojure/) + - [Christopher Bui says...](https://web.archive.org/web/20181223213500/https://cb.codes/what-editor-ide-to-use-for-clojure/) # Course Design Philosophy From 0cc34c33d48990c1978c0a67dcc75605e5969e00 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Sun, 15 Aug 2021 21:36:40 +0530 Subject: [PATCH 49/62] Add intro to Clojure, the language. Thanks Lars Wirzenius who generously reviewed the project's README[1], and noted that a short note on the language would be helpful for people who've never heard of Clojure before. [1] https://liw.fi/readme-review/ --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 30f2f98..5537f91 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,24 @@ # Introduction -What could one do with just a _little_ bit of Clojure? +This workshop aims to get your brain and fingers accustomed to just enough of +the [Clojure](https://clojure.org) programming language to start doing useful things with it. + +In other words, "What could one do with just a _little_ bit of Clojure?". + +## What is Clojure? + +Clojure is an interactive functional programming language that can run on many platforms +like the [JVM](https://clojure.org/about/jvm_hosted), [.NET CLR](https://clojure.org/about/clojureclr), [Javascript](https://clojurescript.org/) (browsers, nodeJS, React Native), as [native binaries](https://github.com/BrunoBonacci/graalvm-clojure) via Graalvm, and even as [shell scripts](https://babashka.org/)! + +It is [used by software teams worldwide](https://clojure.org/community/success_stories#) to deliver +high-value software systems at giant companies like Apple, Walmart, to "decacorns" +like GoJek, Nubank, to a wide array of startups, and one-person businesses like Partsbox.com. + +Its interactivity and dynamism foster a sense of playfulness that attracts all manner +of [creative makers](http://radar.oreilly.com/2015/05/creative-computing-with-clojure.html)---hobbyist as well as serious artists and musicians. + +A small but vibrant [global community](https://clojure.org/community/user_groups) is [busy building amazing things](https://github.com/trending/clojure?since=monthly) with the language. ## Intended usage - Support a 1-day guided workshop for programmers new to Clojure (not absolute programming beginners). From 6390163ffb7f887f107c007168499cba259bd90a Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Thu, 26 Aug 2021 21:58:47 +0530 Subject: [PATCH 50/62] Fix links to 4clojure problem sets. The OG site was discontinued by the OG maintainers, after a decade of keeping the lights on! It has been re-hosted by @oxalorg and @lambdaisland, at https://4clojure.oxal.org Ref: [ANN] Discontinuing 4clojure.com https://groups.google.com/g/clojure/c/ZWmDEzvn-Js --- src/clojure_by_example/ex00_introduction.clj | 4 ++-- src/clojure_by_example/ex01_fundamentally_functional.clj | 2 +- src/clojure_by_example/ex02_domain_as_data.clj | 2 +- src/clojure_by_example/ex03_data_and_functions.clj | 2 +- src/clojure_by_example/ex04_api_design.clj | 2 +- src/clojure_by_example/ex05_immutability_and_fp.clj | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/clojure_by_example/ex00_introduction.clj b/src/clojure_by_example/ex00_introduction.clj index 6319dcf..6f4fd88 100644 --- a/src/clojure_by_example/ex00_introduction.clj +++ b/src/clojure_by_example/ex00_introduction.clj @@ -253,5 +253,5 @@ same ; is defined in the current ns ;; ;; - With the knowledge you have so far, you can try solving these ;; problems at 4clojure.com: 1 to 13, and 16, 47, 126, 161, and 162 -;; e.g. http://www.4clojure.com/problem/1 -;; e.g. http://www.4clojure.com/problem/16 +;; e.g. https://4clojure.oxal.org/#/problem/1 +;; e.g. https://4clojure.oxal.org/#/problem/16 diff --git a/src/clojure_by_example/ex01_fundamentally_functional.clj b/src/clojure_by_example/ex01_fundamentally_functional.clj index a4a8423..cde2352 100644 --- a/src/clojure_by_example/ex01_fundamentally_functional.clj +++ b/src/clojure_by_example/ex01_fundamentally_functional.clj @@ -489,6 +489,6 @@ reduce ;; that 4clojure requires. ;; (comment - (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + (map (fn [problem-no] (str "https://4clojure.oxal.org/#/problem/" problem-no)) [14, 15, 19, 20, 48, 45])) diff --git a/src/clojure_by_example/ex02_domain_as_data.clj b/src/clojure_by_example/ex02_domain_as_data.clj index e760ade..04e1089 100644 --- a/src/clojure_by_example/ex02_domain_as_data.clj +++ b/src/clojure_by_example/ex02_domain_as_data.clj @@ -245,7 +245,7 @@ ;; that 4clojure requires. ;; (comment - (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + (map (fn [problem-no] (str "https://4clojure.oxal.org/#/problem/" problem-no)) [17, 18, 57, 71 134, 27, 26, 39])) diff --git a/src/clojure_by_example/ex03_data_and_functions.clj b/src/clojure_by_example/ex03_data_and_functions.clj index 5be05e8..9165b0d 100644 --- a/src/clojure_by_example/ex03_data_and_functions.clj +++ b/src/clojure_by_example/ex03_data_and_functions.clj @@ -437,7 +437,7 @@ p/target-planets ;; without code-golfing or hacks. Then translate to anonymous function form ;; that 4clojure requires. (comment - (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + (map (fn [problem-no] (str "https://4clojure.oxal.org/#/problem/" problem-no)) [37, 64, 72, 21, 24, 25, 38, 29, 42, 31, 81, 107, diff --git a/src/clojure_by_example/ex04_api_design.clj b/src/clojure_by_example/ex04_api_design.clj index 6050e91..2c23769 100644 --- a/src/clojure_by_example/ex04_api_design.clj +++ b/src/clojure_by_example/ex04_api_design.clj @@ -321,7 +321,7 @@ ;; that 4clojure requires. ;; (comment - (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + (map (fn [problem-no] (str "https://4clojure.oxal.org/#/problem/" problem-no)) [35, 36, 68, 145, 52, 156, 22, 23, 32, 30, diff --git a/src/clojure_by_example/ex05_immutability_and_fp.clj b/src/clojure_by_example/ex05_immutability_and_fp.clj index 4e31e54..b3e771a 100644 --- a/src/clojure_by_example/ex05_immutability_and_fp.clj +++ b/src/clojure_by_example/ex05_immutability_and_fp.clj @@ -276,6 +276,6 @@ other-pi ; what should this be? ;; that 4clojure requires. ;; (comment - (map (fn [problem-no] (str "http://www.4clojure.com/problem/" + (map (fn [problem-no] (str "https://4clojure.oxal.org/#/problem/" problem-no)) [51, 77, 60, 102, 86, 115])) From c8d15071a44406e6823b92689bccd33765067320 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Thu, 2 Sep 2021 10:12:37 +0530 Subject: [PATCH 51/62] Link to alternative setups for VSCode and Vim --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5537f91..9be6a92 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Just do the following one by one, and you should be fine. ## IntelliJ + Cursive IDE -We support IntelliJ + Cursive IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. Avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! +We support IntelliJ + Cursive IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (VSCode, Vim, Emacs, Atom), *but* please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! - Download and Install [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) - Install and configure the Cursive plugin for IntelliJ by following the [official Cursive user guide](https://cursive-ide.com/userguide/). @@ -137,7 +137,9 @@ Follow [Leiningen setup instructions here](https://leiningen.org/). If you can't use IntelliJ for some reason, you may try one of these. Although we haven't tested with these setups, the workshop material should work fine. You'll also have to install Leiningen and Java separately. + - [VSCode + Calva](https://calva.io/) has become a fantastic Clojure IDE! - A [snazzy setup with Atom](https://medium.com/@jacekschae/slick-clojure-editor-setup-with-atom-a3c1b528b722). + - [Vim + vim-fireplace](https://thoughtbot.com/blog/writing-clojure-in-vim) and other goodies that make Clojure/Lisp hacking fun in Vim. - Brave Clojure walks you through [a basic Emacs setup for learning Clojure](https://www.braveclojure.com/basic-emacs/). From 47eb98f721aae1996d95b1d9b4fd5a9f8cc62f1c Mon Sep 17 00:00:00 2001 From: Kiran Kulkarni Date: Wed, 22 Nov 2023 22:59:31 +0530 Subject: [PATCH 52/62] Move to tools.deps i.e. deps.edn --- deps.edn | 5 +++++ project.clj | 11 ----------- 2 files changed, 5 insertions(+), 11 deletions(-) create mode 100644 deps.edn delete mode 100644 project.clj diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..829f18a --- /dev/null +++ b/deps.edn @@ -0,0 +1,5 @@ +{:paths ["src" "resources"], + :deps {org.clojure/clojure {:mvn/version "1.10.0"}} + :aliases {:dev {:extra-deps {org.clojure/data.json {:mvn/version "0.2.6"} + enlive/enlive {:mvn/version "1.1.6"} + rewrite-clj/rewrite-clj {:mvn/version "0.6.1"}}}}} diff --git a/project.clj b/project.clj deleted file mode 100644 index 2c5b89c..0000000 --- a/project.clj +++ /dev/null @@ -1,11 +0,0 @@ -(defproject clojure_by_example "0.1.0-SNAPSHOT" - :description "A workshop to introduce Clojure, to programmers new to Clojure." - :url "https://github.com/inclojure-org/clojure-by-example" - :license {:name "MIT" - :url "https://opensource.org/licenses/MIT"} - ;; Requirements: Java 8 or higher (recommended: Java 8 or Java 11) - :dependencies [[org.clojure/clojure "1.10.0"]] - :local-repo ".m2-for-inclojure" - :profiles {:dev {:dependencies [[org.clojure/data.json "0.2.6"] - [enlive "1.1.6"] - [rewrite-clj "0.6.1"]]}}) From fe12e0e3fa1ac38c2e0833aa04ce1393d5214fb0 Mon Sep 17 00:00:00 2001 From: Kiran Kulkarni Date: Thu, 23 Nov 2023 18:17:15 +0530 Subject: [PATCH 53/62] Remove the dev alias and add all dependencies to default --- deps.edn | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deps.edn b/deps.edn index 829f18a..394857e 100644 --- a/deps.edn +++ b/deps.edn @@ -1,5 +1,5 @@ {:paths ["src" "resources"], - :deps {org.clojure/clojure {:mvn/version "1.10.0"}} - :aliases {:dev {:extra-deps {org.clojure/data.json {:mvn/version "0.2.6"} - enlive/enlive {:mvn/version "1.1.6"} - rewrite-clj/rewrite-clj {:mvn/version "0.6.1"}}}}} + :deps {org.clojure/clojure {:mvn/version "1.10.0"} + org.clojure/data.json {:mvn/version "0.2.6"} + enlive/enlive {:mvn/version "1.1.6"} + rewrite-clj/rewrite-clj {:mvn/version "0.6.1"}}} From c4146708754bf8dd3fa762d515f3925a82adf7fa Mon Sep 17 00:00:00 2001 From: Kiran Kulkarni Date: Thu, 23 Nov 2023 18:25:45 +0530 Subject: [PATCH 54/62] Add VSCode + Calva section in README.md --- README.md | 181 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 103 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 9be6a92..b4ede29 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ - - [Introduction](#introduction) - - [Intended usage](#intended-usage) - - [Contributions](#contributions) - - [Workshop Goals](#workshop-goals) - - [Workshop Anti-Goals](#workshop-anti-goals) +- [Introduction](#introduction) + - [Intended usage](#intended-usage) + - [Contributions](#contributions) + - [Workshop Goals](#workshop-goals) + - [Workshop Anti-Goals](#workshop-anti-goals) - [Suggested learning mindset](#suggested-learning-mindset) - [Setup Instructions](#setup-instructions) - [Course Design Philosophy](#course-design-philosophy) - [Credits](#credits) - [Copyright and License](#copyright-and-license) - # Introduction This workshop aims to get your brain and fingers accustomed to just enough of @@ -20,7 +19,7 @@ In other words, "What could one do with just a _little_ bit of Clojure?". ## What is Clojure? Clojure is an interactive functional programming language that can run on many platforms -like the [JVM](https://clojure.org/about/jvm_hosted), [.NET CLR](https://clojure.org/about/clojureclr), [Javascript](https://clojurescript.org/) (browsers, nodeJS, React Native), as [native binaries](https://github.com/BrunoBonacci/graalvm-clojure) via Graalvm, and even as [shell scripts](https://babashka.org/)! +like the [JVM](https://clojure.org/about/jvm_hosted), [.NET CLR](https://clojure.org/about/clojureclr), [Javascript](https://clojurescript.org/) (browsers, nodeJS, React Native), as [native binaries](https://github.com/BrunoBonacci/graalvm-clojure) via Graalvm, and even as [shell scripts](https://babashka.org/)! It is [used by software teams worldwide](https://clojure.org/community/success_stories#) to deliver high-value software systems at giant companies like Apple, Walmart, to "decacorns" @@ -32,48 +31,51 @@ of [creative makers](http://radar.oreilly.com/2015/05/creative-computing-with-cl A small but vibrant [global community](https://clojure.org/community/user_groups) is [busy building amazing things](https://github.com/trending/clojure?since=monthly) with the language. ## Intended usage - - Support a 1-day guided workshop for programmers new to Clojure (not absolute programming beginners). - - Also function as at-home learning material for said programmers. - - The `master` branch is heavily commented, for at-home use - - A `solutions` branch will be available, as a companion to `master`. - But don't peek at it in advance! - - You may see a `workshop-code` branch. Ignore it. It is meant only for - workshop use, and is subject to deletion/re-creation. - - Incidentally, if you landed here while searching for Hirokuni Kim's -"[Clojure By Example](https://kimh.github.io/clojure-by-example/)", well, follow the link! + +- Support a 1-day guided workshop for programmers new to Clojure (not absolute programming beginners). +- Also function as at-home learning material for said programmers. +- The `master` branch is heavily commented, for at-home use +- A `solutions` branch will be available, as a companion to `master`. + But don't peek at it in advance! +- You may see a `workshop-code` branch. Ignore it. It is meant only for + workshop use, and is subject to deletion/re-creation. +- Incidentally, if you landed here while searching for Hirokuni Kim's + "[Clojure By Example](https://kimh.github.io/clojure-by-example/)", well, follow the link! ## Contributions - - If you find bugs or errors, please send a PR (but please - don't change the course structure or pedagogy). + +- If you find bugs or errors, please send a PR (but please + don't change the course structure or pedagogy). ## Workshop Goals - - Acquire a "feel" of Clojure, for further self-study/exploration. - - Learn how Clojurists usually think with Clojure to solve problems. - - See how it's not so hard to do surprisingly powerful things with a - mere handful of "primitive" functions, data structures, and ideas. - - Get you started with a good development setup and workflow that will - serve you well if (when) you continue to program with Clojure, as a - hobby, or at work! + +- Acquire a "feel" of Clojure, for further self-study/exploration. +- Learn how Clojurists usually think with Clojure to solve problems. +- See how it's not so hard to do surprisingly powerful things with a + mere handful of "primitive" functions, data structures, and ideas. +- Get you started with a good development setup and workflow that will + serve you well if (when) you continue to program with Clojure, as a + hobby, or at work! ## Workshop Anti-Goals - - Try to explain Functional Programming theory or Clojure's innards. - (Many free and paid tutorials and books do so very well.) - - Try to fully cover Clojure primitives/features. (That's homework!) - - Devolve into language wars, editor wars, syntax wars, type wars... - (Life's too short, people.) - - Focus too much on tooling or operational things. (At least not - while there's fun to be had!) +- Try to explain Functional Programming theory or Clojure's innards. + (Many free and paid tutorials and books do so very well.) +- Try to fully cover Clojure primitives/features. (That's homework!) +- Devolve into language wars, editor wars, syntax wars, type wars... + (Life's too short, people.) +- Focus too much on tooling or operational things. (At least not + while there's fun to be had!) # Suggested learning mindset - - Think of this as an exercise in "constrained creativity". - - Ignore details, achieve much with as little know-how as possible. - - Focus on what things do; not what they are, or why they are. - - Inform your _intuition for doing things_, and then use that to - dive deeper into all the juicy details at your own pace, later. -Take what is useful, discard the rest. +- Think of this as an exercise in "constrained creativity". +- Ignore details, achieve much with as little know-how as possible. +- Focus on what things do; not what they are, or why they are. +- Inform your _intuition for doing things_, and then use that to + dive deeper into all the juicy details at your own pace, later. +Take what is useful, discard the rest. # Setup Instructions @@ -81,37 +83,62 @@ It's a liiitle bit of work. But not too bad. Just do the following one by one, and you should be fine. +## VSCode + Calva + +We support VSCode + Calva IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (VSCode, Vim, Emacs, Atom), _but_ please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! + +- Download and Install [VSCode](https://code.visualstudio.com/) +- Open VSCode and complete the initialization process. +- Open the "Extensions" Tab and search for "Calva", Install the "Calva: Clojure & ClojureScript Interactive Programming" extension + - Alternatively you can visit the [Calva page](https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva) to install it +- You need Java Installed + - Run `java -version` in your terminal. + - If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). Any version should do. + - Once you are done, `java -version` should show you a Java version. + +Once installed: + +- Clone the repository on your machine +- In VSCode Use File > Open Folder... and open the cloned folder +- Notice that Calva activates +- Open the [Command Pallete](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) in VSCode using `⇧⌘P` on Mac or `Ctrl+Shift+P` on other systems +- Type "Calva: Start Project REPL" and choose "Calva: Start a Project REPL and Connect (aka Jack-In)" from the list + - Select `deps.edn` when prompted for Project type. We are using [tools.deps](https://clojure.org/guides/deps_and_cli) for managing the project. You don't need to worry about it's details for this workshop + - VSCode will create a new pane called 'output.calva-repl' and you will see `clj꞉user꞉>` prompt in that screen +- You have a working REPL now! + ## IntelliJ + Cursive IDE -We support IntelliJ + Cursive IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (VSCode, Vim, Emacs, Atom), *but* please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! +We support IntelliJ + Cursive IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (VSCode, Vim, Emacs, Atom), _but_ please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! - - Download and Install [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) - - Install and configure the Cursive plugin for IntelliJ by following the [official Cursive user guide](https://cursive-ide.com/userguide/). +- Download and Install [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) +- Install and configure the Cursive plugin for IntelliJ by following the [official Cursive user guide](https://cursive-ide.com/userguide/). Once installed: - - Launch IntelliJ and select "Import Project" from the opening splash screen. - - OR use IntelliJ's file menu to open this project via File > New > Project From Existing Sources - - Select this project's main directory; click OK - - The "Import Project" dialog box should open - - Select Leiningen under "Import project from external model"; click Next - - Click Next again in the following screen that shows "Root Directory"; wait for it... - - Again, click Next in the screen that says "Select Leiningen projects to import" - - And again, click Next in the "Please select project SDK" screen (ensure you select JDK version 1.8 or higher) - - Click "Finish", and wait for IntelliJ to set up the project - - Under `Run...` click on `Run...` and then select `REPL for clojure-by-example` (or whatever your project name happens to be). - - A right pane should open, with a REPL session. - - Now, open the `ex00..` file under the `src` folder - - Use the menu under Tools > REPL to (a) Switch to the file's "namespace", and then (b) load the file into the REPL - - Scroll down a little, till you see `(+ 1 2)`. - - Place your cursor after the closing parenthesis `)`, then right-click to open the context menu, and click on REPL > "Send '(+ 1 2)' to the REPL. - - You should see '(+ 1 2)' appear in the REPL window, followed by `3`. This means you successfully evaluated an expression in the REPL. - - Now you may start from the top of ex00 and work through the material in each "ex" file - - Important: For every exercise file, remember to first switch to the file's namespace, and load the file in the REPL (use the menu under Tools > REPL) +- Launch IntelliJ and select "Import Project" from the opening splash screen. +- OR use IntelliJ's file menu to open this project via File > New > Project From Existing Sources +- Select this project's main directory; click OK +- The "Import Project" dialog box should open +- Select Leiningen under "Import project from external model"; click Next +- Click Next again in the following screen that shows "Root Directory"; wait for it... +- Again, click Next in the screen that says "Select Leiningen projects to import" +- And again, click Next in the "Please select project SDK" screen (ensure you select JDK version 1.8 or higher) +- Click "Finish", and wait for IntelliJ to set up the project +- Under `Run...` click on `Run...` and then select `REPL for clojure-by-example` (or whatever your project name happens to be). +- A right pane should open, with a REPL session. +- Now, open the `ex00..` file under the `src` folder +- Use the menu under Tools > REPL to (a) Switch to the file's "namespace", and then (b) load the file into the REPL +- Scroll down a little, till you see `(+ 1 2)`. +- Place your cursor after the closing parenthesis `)`, then right-click to open the context menu, and click on REPL > "Send '(+ 1 2)' to the REPL. +- You should see '(+ 1 2)' appear in the REPL window, followed by `3`. This means you successfully evaluated an expression in the REPL. +- Now you may start from the top of ex00 and work through the material in each "ex" file +- Important: For every exercise file, remember to first switch to the file's namespace, and load the file in the REPL (use the menu under Tools > REPL) Also keep the Cursive user guide handy, in case you need editor help, as you solve the workshop material. In particular, the [Paredit guide](https://cursive-ide.com/userguide/paredit.html) may be useful, if you stumble when editing Clojure code. ## (Optional) Java and Leiningen + Being a JVM hosted language, Clojure requires Java to run. For the workshop, we also use a Clojure build tool called Leiningen. If you're using IntelliJ + Cursive for the workshop, you won't need to install Java or Leiningen separately, since Intellij will come with a JDK and Cursive will download Leiningen when you import the project. So just follow the IntelliJ + Cursive setup guide and you'll be good to go for the workshop. @@ -120,13 +147,13 @@ If you're working on a production project however, it's useful to have both Java ### Java - - Run `java -version` in your terminal. - - If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). Any version should do. - - Once you are done, `java -version` should show you a Java version. +- Run `java -version` in your terminal. +- If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). Any version should do. +- Once you are done, `java -version` should show you a Java version. Notes: - - We have not tested this project with Java 7. +- We have not tested this project with Java 7. ### Leiningen @@ -137,20 +164,18 @@ Follow [Leiningen setup instructions here](https://leiningen.org/). If you can't use IntelliJ for some reason, you may try one of these. Although we haven't tested with these setups, the workshop material should work fine. You'll also have to install Leiningen and Java separately. - - [VSCode + Calva](https://calva.io/) has become a fantastic Clojure IDE! - - A [snazzy setup with Atom](https://medium.com/@jacekschae/slick-clojure-editor-setup-with-atom-a3c1b528b722). - - [Vim + vim-fireplace](https://thoughtbot.com/blog/writing-clojure-in-vim) and other goodies that make Clojure/Lisp hacking fun in Vim. - - Brave Clojure walks you through [a basic Emacs setup for learning Clojure](https://www.braveclojure.com/basic-emacs/). - +- [VSCode + Calva](https://calva.io/) has become a fantastic Clojure IDE! +- A [snazzy setup with Atom](https://medium.com/@jacekschae/slick-clojure-editor-setup-with-atom-a3c1b528b722). +- [Vim + vim-fireplace](https://thoughtbot.com/blog/writing-clojure-in-vim) and other goodies that make Clojure/Lisp hacking fun in Vim. +- Brave Clojure walks you through [a basic Emacs setup for learning Clojure](https://www.braveclojure.com/basic-emacs/). ## Your favourite editor: You may find instructions for your favourite editor at one of these pages. But there are only so many choices. Ultimately, you must pick your poison and run with it: - - ["Clojure Tools" at clojure.org](https://clojure.org/community/tools) - - ["Essentials" at clojure-doc.org](http://clojure-doc.org/articles/content.html#essentials) - - [Christopher Bui says...](https://web.archive.org/web/20181223213500/https://cb.codes/what-editor-ide-to-use-for-clojure/) - +- ["Clojure Tools" at clojure.org](https://clojure.org/community/tools) +- ["Essentials" at clojure-doc.org](http://clojure-doc.org/articles/content.html#essentials) +- [Christopher Bui says...](https://web.archive.org/web/20181223213500/https://cb.codes/what-editor-ide-to-use-for-clojure/) # Course Design Philosophy @@ -167,14 +192,14 @@ satisfies and empowers us deeply. So, may you stay small and achieve important things. Live long, and prosper. -\\\\//_ - +\\\\//\_ # Credits - - [clj-pune](https://github.com/clj-pune) people, especially [kapilreddy](https://github.com/kapilreddy), and [jaju](https://github.com/jaju) for critique while making ["pratham"](https://github.com/clj-pune/pratham), the precursor to this project. - - [adityaathalye](https://github.com/adityaathalye), [jysandy](https://github.com/jysandy), and [kapilreddy](https://github.com/kapilreddy) for course design, code reviews, critique, commits, and being the core teaching staff at the first edition of this workshop at IN/Clojure 2018. - - All the workshop participants, and the many Clojurists who generously donated their time to make it successful. - - [inclojure-org](https://github.com/inclojure-org) for being the umbrella under which this work happened. + +- [clj-pune](https://github.com/clj-pune) people, especially [kapilreddy](https://github.com/kapilreddy), and [jaju](https://github.com/jaju) for critique while making ["pratham"](https://github.com/clj-pune/pratham), the precursor to this project. +- [adityaathalye](https://github.com/adityaathalye), [jysandy](https://github.com/jysandy), and [kapilreddy](https://github.com/kapilreddy) for course design, code reviews, critique, commits, and being the core teaching staff at the first edition of this workshop at IN/Clojure 2018. +- All the workshop participants, and the many Clojurists who generously donated their time to make it successful. +- [inclojure-org](https://github.com/inclojure-org) for being the umbrella under which this work happened. ## Copyright and License From 50a611e34fb3ea87f0cef297ab117ca2e267c9e7 Mon Sep 17 00:00:00 2001 From: Kapil Reddy Date: Fri, 24 Nov 2023 17:55:34 +0530 Subject: [PATCH 55/62] Update - Simplify setup instructions - Add VSCode + Calva as primary setup - Remove other additional setup instructions --- README.md | 58 +++---------------------------------------------------- 1 file changed, 3 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index b4ede29..a59f950 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,6 @@ Take what is useful, discard the rest. # Setup Instructions -It's a liiitle bit of work. But not too bad. - Just do the following one by one, and you should be fine. ## VSCode + Calva @@ -91,7 +89,7 @@ We support VSCode + Calva IDE in the classroom for this workshop. We suggest you - Open VSCode and complete the initialization process. - Open the "Extensions" Tab and search for "Calva", Install the "Calva: Clojure & ClojureScript Interactive Programming" extension - Alternatively you can visit the [Calva page](https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva) to install it -- You need Java Installed +- You need Java Installed - Run `java -version` in your terminal. - If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). Any version should do. - Once you are done, `java -version` should show you a Java version. @@ -105,45 +103,9 @@ Once installed: - Type "Calva: Start Project REPL" and choose "Calva: Start a Project REPL and Connect (aka Jack-In)" from the list - Select `deps.edn` when prompted for Project type. We are using [tools.deps](https://clojure.org/guides/deps_and_cli) for managing the project. You don't need to worry about it's details for this workshop - VSCode will create a new pane called 'output.calva-repl' and you will see `clj꞉user꞉>` prompt in that screen -- You have a working REPL now! - -## IntelliJ + Cursive IDE - -We support IntelliJ + Cursive IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (VSCode, Vim, Emacs, Atom), _but_ please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! - -- Download and Install [IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) -- Install and configure the Cursive plugin for IntelliJ by following the [official Cursive user guide](https://cursive-ide.com/userguide/). - -Once installed: +- You have a working REPL now! -- Launch IntelliJ and select "Import Project" from the opening splash screen. -- OR use IntelliJ's file menu to open this project via File > New > Project From Existing Sources -- Select this project's main directory; click OK -- The "Import Project" dialog box should open -- Select Leiningen under "Import project from external model"; click Next -- Click Next again in the following screen that shows "Root Directory"; wait for it... -- Again, click Next in the screen that says "Select Leiningen projects to import" -- And again, click Next in the "Please select project SDK" screen (ensure you select JDK version 1.8 or higher) -- Click "Finish", and wait for IntelliJ to set up the project -- Under `Run...` click on `Run...` and then select `REPL for clojure-by-example` (or whatever your project name happens to be). -- A right pane should open, with a REPL session. -- Now, open the `ex00..` file under the `src` folder -- Use the menu under Tools > REPL to (a) Switch to the file's "namespace", and then (b) load the file into the REPL -- Scroll down a little, till you see `(+ 1 2)`. -- Place your cursor after the closing parenthesis `)`, then right-click to open the context menu, and click on REPL > "Send '(+ 1 2)' to the REPL. -- You should see '(+ 1 2)' appear in the REPL window, followed by `3`. This means you successfully evaluated an expression in the REPL. -- Now you may start from the top of ex00 and work through the material in each "ex" file -- Important: For every exercise file, remember to first switch to the file's namespace, and load the file in the REPL (use the menu under Tools > REPL) - -Also keep the Cursive user guide handy, in case you need editor help, as you solve the workshop material. In particular, the [Paredit guide](https://cursive-ide.com/userguide/paredit.html) may be useful, if you stumble when editing Clojure code. - -## (Optional) Java and Leiningen - -Being a JVM hosted language, Clojure requires Java to run. For the workshop, we also use a Clojure build tool called Leiningen. -If you're using IntelliJ + Cursive for the workshop, you won't need to install Java or Leiningen separately, since Intellij will come with a JDK and Cursive will download Leiningen when you import the project. -So just follow the IntelliJ + Cursive setup guide and you'll be good to go for the workshop. - -If you're working on a production project however, it's useful to have both Java and Leiningen separately installed. +- Keep the [Paredit guide](https://calva.io/paredit/) handy, editing code will require some understanding of paredit. ### Java @@ -155,20 +117,6 @@ Notes: - We have not tested this project with Java 7. -### Leiningen - -Follow [Leiningen setup instructions here](https://leiningen.org/). - -## Alternative Starter Kits: - -If you can't use IntelliJ for some reason, you may try one of these. Although we haven't tested with these setups, the workshop material should work fine. -You'll also have to install Leiningen and Java separately. - -- [VSCode + Calva](https://calva.io/) has become a fantastic Clojure IDE! -- A [snazzy setup with Atom](https://medium.com/@jacekschae/slick-clojure-editor-setup-with-atom-a3c1b528b722). -- [Vim + vim-fireplace](https://thoughtbot.com/blog/writing-clojure-in-vim) and other goodies that make Clojure/Lisp hacking fun in Vim. -- Brave Clojure walks you through [a basic Emacs setup for learning Clojure](https://www.braveclojure.com/basic-emacs/). - ## Your favourite editor: You may find instructions for your favourite editor at one of these pages. But there are only so many choices. Ultimately, you must pick your poison and run with it: From 2d2be6601f5a931c1c8283e6a776535b4c4b9d05 Mon Sep 17 00:00:00 2001 From: Aniket Hendre Date: Sat, 25 Nov 2023 18:46:02 +0530 Subject: [PATCH 56/62] Add calva generated directories to gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 27613b2..fbc11d8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,7 @@ profiles.clj .idea .m2-for-inclojure *.iml +.lsp +.cpcache +.clj-kondo +.calva From 30bfce980b459e7ce6e6538bfe3af644fd1eac92 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 4 Mar 2024 14:14:13 +0530 Subject: [PATCH 57/62] Fix typos. Replace redundant reference to VSCode, now that we have switched to it over IntelliJ. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a59f950..7fa3191 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Just do the following one by one, and you should be fine. ## VSCode + Calva -We support VSCode + Calva IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (VSCode, Vim, Emacs, Atom), _but_ please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! +We support VSCode + Calva IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (IntelliJ, Vim, Emacs, Atom), _but_ please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! - Download and Install [VSCode](https://code.visualstudio.com/) - Open VSCode and complete the initialization process. @@ -99,7 +99,7 @@ Once installed: - Clone the repository on your machine - In VSCode Use File > Open Folder... and open the cloned folder - Notice that Calva activates -- Open the [Command Pallete](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) in VSCode using `⇧⌘P` on Mac or `Ctrl+Shift+P` on other systems +- Open the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) in VSCode using `⇧⌘P` on Mac or `Ctrl+Shift+P` on other systems - Type "Calva: Start Project REPL" and choose "Calva: Start a Project REPL and Connect (aka Jack-In)" from the list - Select `deps.edn` when prompted for Project type. We are using [tools.deps](https://clojure.org/guides/deps_and_cli) for managing the project. You don't need to worry about it's details for this workshop - VSCode will create a new pane called 'output.calva-repl' and you will see `clj꞉user꞉>` prompt in that screen From 189d847c4e297c8c8d5381801997e223b87e3110 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 4 Mar 2024 14:15:47 +0530 Subject: [PATCH 58/62] Deduplicate Java install instruction. Move it above IDE install instructions. --- README.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7fa3191..2c11bf1 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,18 @@ Take what is useful, discard the rest. Just do the following one by one, and you should be fine. +## Java + +You need Java installed. + +- Run `java -version` in your terminal. +- If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). Any version should do. +- Once you are done, `java -version` should show you a Java version. + +Notes: + +- We have not tested this project with Java 7. + ## VSCode + Calva We support VSCode + Calva IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (IntelliJ, Vim, Emacs, Atom), _but_ please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! @@ -89,10 +101,6 @@ We support VSCode + Calva IDE in the classroom for this workshop. We suggest you - Open VSCode and complete the initialization process. - Open the "Extensions" Tab and search for "Calva", Install the "Calva: Clojure & ClojureScript Interactive Programming" extension - Alternatively you can visit the [Calva page](https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva) to install it -- You need Java Installed - - Run `java -version` in your terminal. - - If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). Any version should do. - - Once you are done, `java -version` should show you a Java version. Once installed: @@ -107,16 +115,6 @@ Once installed: - Keep the [Paredit guide](https://calva.io/paredit/) handy, editing code will require some understanding of paredit. -### Java - -- Run `java -version` in your terminal. -- If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). Any version should do. -- Once you are done, `java -version` should show you a Java version. - -Notes: - -- We have not tested this project with Java 7. - ## Your favourite editor: You may find instructions for your favourite editor at one of these pages. But there are only so many choices. Ultimately, you must pick your poison and run with it: From 50854a67ce3e5dd8f00b14efce7880f13f2e0157 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 4 Mar 2024 14:17:53 +0530 Subject: [PATCH 59/62] Consistently period-terminate bullet points, as we are using sentences over phrases. Either style is fine, as long as it is consistent. --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2c11bf1..686e5f2 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ A small but vibrant [global community](https://clojure.org/community/user_groups - Support a 1-day guided workshop for programmers new to Clojure (not absolute programming beginners). - Also function as at-home learning material for said programmers. -- The `master` branch is heavily commented, for at-home use +- The `master` branch is heavily commented, for at-home use. - A `solutions` branch will be available, as a companion to `master`. But don't peek at it in advance! - You may see a `workshop-code` branch. Ignore it. It is meant only for @@ -97,20 +97,21 @@ Notes: We support VSCode + Calva IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (IntelliJ, Vim, Emacs, Atom), _but_ please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! -- Download and Install [VSCode](https://code.visualstudio.com/) +- Download and Install [VSCode](https://code.visualstudio.com/). - Open VSCode and complete the initialization process. -- Open the "Extensions" Tab and search for "Calva", Install the "Calva: Clojure & ClojureScript Interactive Programming" extension - - Alternatively you can visit the [Calva page](https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva) to install it +- Open the "Extensions" Tab and search for "Calva", Install the "Calva: + Clojure & ClojureScript Interactive Programming" extension. +- Alternatively you can visit the [Calva page](https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva) to install it. Once installed: -- Clone the repository on your machine -- In VSCode Use File > Open Folder... and open the cloned folder -- Notice that Calva activates -- Open the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) in VSCode using `⇧⌘P` on Mac or `Ctrl+Shift+P` on other systems -- Type "Calva: Start Project REPL" and choose "Calva: Start a Project REPL and Connect (aka Jack-In)" from the list - - Select `deps.edn` when prompted for Project type. We are using [tools.deps](https://clojure.org/guides/deps_and_cli) for managing the project. You don't need to worry about it's details for this workshop - - VSCode will create a new pane called 'output.calva-repl' and you will see `clj꞉user꞉>` prompt in that screen +- Clone the repository on your machine. +- In VSCode Use File > Open Folder... and open the cloned folder. +- Notice that Calva activates. +- Open the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) in VSCode using `⇧⌘P` on Mac or `Ctrl+Shift+P` on other systems. +- Type "Calva: Start Project REPL" and choose "Calva: Start a Project REPL and Connect (aka Jack-In)" from the list. + - Select `deps.edn` when prompted for Project type. We are using [tools.deps](https://clojure.org/guides/deps_and_cli) for managing the project. You don't need to worry about it's details for this workshop. + - VSCode will create a new pane called 'output.calva-repl' and you will see `clj꞉user꞉>` prompt in that screen. - You have a working REPL now! - Keep the [Paredit guide](https://calva.io/paredit/) handy, editing code will require some understanding of paredit. From a76d055eaa6dee56e974677103089343b9796477 Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 4 Mar 2024 14:26:24 +0530 Subject: [PATCH 60/62] Tighten Java install instructions --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 686e5f2..c12566b 100644 --- a/README.md +++ b/README.md @@ -86,13 +86,11 @@ Just do the following one by one, and you should be fine. You need Java installed. - Run `java -version` in your terminal. -- If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). Any version should do. +- If Java is not installed, please [download and install Java from here](https://adoptopenjdk.net/). +- Any version should do, but prefer Java 8 or higher. We have not tested + this project with Java 7. - Once you are done, `java -version` should show you a Java version. -Notes: - -- We have not tested this project with Java 7. - ## VSCode + Calva We support VSCode + Calva IDE in the classroom for this workshop. We suggest you use this setup, unless of course, you have already configured your favourite editor for Clojure development. We've listed alternate starter kits below (IntelliJ, Vim, Emacs, Atom), _but_ please avoid [bikeshedding](http://catb.org/jargon/html/B/bikeshedding.html) editors. Just complete the workshop first! From 37e5c09ef175e5efe674c83f64ec5276332311fa Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 4 Mar 2024 14:37:40 +0530 Subject: [PATCH 61/62] Fix copyright notice, up to 2024 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c12566b..be8f605 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,6 @@ Live long, and prosper. ## Copyright and License -Copyright © 2017-2018 [IN/Clojure](http://inclojure.org/). +Copyright © 2017-2024 [IN/Clojure](http://inclojure.org/). Distributed under the [MIT license](https://github.com/inclojure-org/clojure-by-example/blob/master/LICENSE). From 0375b255d6f2d3dc6bfb98036cb0b932b63b1ebb Mon Sep 17 00:00:00 2001 From: Aditya Athalye Date: Mon, 4 Mar 2024 14:49:52 +0530 Subject: [PATCH 62/62] Fix bullet point line space rendering. The blank line caused rendered text to display each bullet point as a paragraph, instead of a list item. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index be8f605..730b1dc 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,6 @@ Once installed: - Select `deps.edn` when prompted for Project type. We are using [tools.deps](https://clojure.org/guides/deps_and_cli) for managing the project. You don't need to worry about it's details for this workshop. - VSCode will create a new pane called 'output.calva-repl' and you will see `clj꞉user꞉>` prompt in that screen. - You have a working REPL now! - - Keep the [Paredit guide](https://calva.io/paredit/) handy, editing code will require some understanding of paredit. ## Your favourite editor: