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