Last active
December 22, 2021 23:13
-
-
Save sritchie/810b898891aa6b9d0810ee0700645cf0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns tutorial.petageconvert) | |
;; woohoo, some comments! | |
;; | |
;; A note about `pet-multiplier`... for your keys, symbols are great, but | |
;; Clojure provides an additional idea of keywords over Scheme. Keywords like | |
;; `:dog` evaluate to themselves, so you don't have to quote them. Symbols are fine, but you have one less thing to remember (the quote). | |
(def pet-multipler | |
{'dog 7 'cat 5 'fish 10}) | |
;; with keywords: | |
(def pet-multipler-keywords | |
{:dog 7 | |
:cat 5 | |
:fish 10}) | |
(comment | |
;; Just for fun, notice that maps can sit in the function position. They take | |
;; a key and return either the value if the key exists, or nil. | |
(pet-multipler 'dog) | |
;; => 7 | |
;; Another fun thing is that keywords and symbols both can act as functions. | |
;; If you pass them a map, they will try and look themselves up in the map: | |
('dog pet-multipler) | |
;; => 7 | |
;; If you pass a second argument, it's returned if the key is not in the map. | |
('dogecoin pet-multipler "not-found") | |
;;=> "not-found" | |
(pet-multipler 'dogecoin "not-found") | |
;;=> "not-found" | |
) | |
;; This is good: | |
(defn human-age [pet age] | |
(* (get pet-multipler pet) age)) | |
(comment | |
;; Notice that if `pet` is not in the map, this will fail, since `(* nil | |
;; <age>)` will not work. BUT you could decide that a good default age is `0`, | |
;; and write the function to returna default multiplier of 0: | |
(defn human-age [pet age] | |
(* (pet-multipler pet 0) age)) | |
(human-age :cactus 12) | |
;; => 0 | |
;; If multiplying by 0 was expensive, you could write the function differently | |
;; to skip the final multiplication in the case of the key not being found: | |
(defn human-age [pet age] | |
(if-let [mult (pet-multipler pet)] | |
(* mult age) | |
0)) | |
;; or you could error in the <else> position, or take an `<on-failure>` | |
;; function argument if this was important enough that you wanted to let users | |
;; control the default. | |
;; now you are into good design areas. | |
) | |
;; @bfeld here is a challenge - | |
;; take two pairs of animal symbol and age, | |
;; and return the pair that is oldest | |
;; (after the conversion for the species)" | |
;; This is great, just one modification to suggest. Note that this is taking | |
;; FOUR arguments, basically the pet and age flattened down. | |
(defn compare-human-age | |
[pet1 age1 pet2 age2] | |
(if (> (human-age pet1 age1) (human-age pet2 age2)) | |
[pet1 age1] | |
[pet2 age2] | |
) | |
) | |
(comment | |
;; You can use Clojure's "destructuring" to write basically the same function, | |
;; but take TWO vectors instead of four arguments, and rip them open right in | |
;; the function's argument vector (I'll say why in a moment): | |
(defn compare-human-age | |
;; spot the difference? | |
[[pet1 age1] [pet2 age2]] | |
(if (> (human-age pet1 age1) | |
(human-age pet2 age2)) | |
[pet1 age1] | |
[pet2 age2])) | |
;; The advantage of this is that if you want to use `reduce`, you'll need a | |
;; function that can take a list of items, and pairwise combine them down | |
;; until there is just one left. So `compare-human-age` works naturally now to | |
;; find the max age of any number of animals. I will use the `& pairs` syntax | |
;; to allow any number of inputs. They all get stuffed into a list and bound | |
;; to `pairs`. | |
(defn max-human-age [& pairs] | |
(reduce compare-human-age pairs)) | |
(max-human-age | |
['dog 12] | |
['fish 10] | |
['cat 3]) | |
;; => ['fish 10] | |
;; this is a nice pattern. If you can compare 2 things, you can compare any | |
;; number. | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Notes from Discord:
There is a Cons cell! https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Cons.java#L22
@bfeld
(cons a b)
here is a function that will build SOMETHING that, when you call first on it, will returna
, and when you callrest
on it will returnb
.so, just like Scheme. Clojure is built on a small number of data structure abstractions, as you noted in the gist. You can of course add more but if you add custom types, you often try hard to make them ACT like set, map, vector or a generic seq (sequence)
and sets, maps and vectors are sequences too (in the map case, a sequence of
[k v]
pairs), so sequence really is the ur-abstraction for sequential things.sequences act like your mental model of lists, always, no matter what the data structure backing the sequence. So if you go make a new type and implement the "sequence" interface, you had better follow that convention. That is why
(cons a vector)
adds to the left.OH, and it does not ONLY add to the left - it does not return a vector anymore! it returns a sequence. you no longer get to do random-access lookups, because you are now in sequence land.
The latter note is the problem. If you are using a vector explicitly, you probably want to STAY in vector land.
So
conj
is a more fine-grained "add to the collection in the most efficient way, but KEEP it the same type (don't convert to sequence).vectors
conj
at the end because if they didn't, then the random-access lookup indices would all have to increment by one.(nth v 2)
would change what it returns, and if you are adding things in by index and looking them up that would be a no-no. "sequences" like linked listsconj
at the front because that is the most efficient thing for them.cons
adds to the beginning of the collection, as noted, but returns a seq, which I think can be whatever clojure deems the most efficient type for the action. (In my experience this is always a list)This would be a
LazySeq
, orCons
, if you had an infinite sequence you werecons
ing onto like:@bfeld here is a design rule of thumb:
map
,reduce
,mapcat
,filter
, etc all follow this. The bonus is you can use the->>
threading macro nicely here, likeassoc
,conj
,nth
,get
etc: