Created
March 20, 2014 03:11
-
-
Save daveyarwood/9656455 to your computer and use it in GitHub Desktop.
This file contains 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
; exs. 32-34: | |
; not applicable to Clojure (Ruby class inheritance) | |
; although this is somewhat similar to implementing protocols in records | |
; ex. 35: | |
(defn mail-them-a-kit [address] | |
(when-not (instance? address ex.Address) | |
(throw (IllegalArgumentException. "No Address object found."))) | |
(print (formatted address))) | |
; ex. 36: | |
; this kind of monkey-patching isn't really available in Clojure... | |
; the way to do it would be to create a new protocol/record that takes | |
; an ordinary array as an argument and implements this modified "join" | |
; function on the array instead of e.g. clojure.string/join | |
(defprotocol ArrayPlus | |
(join [a] [a sep] [a sep frmt] | |
"Joins strings together into a formatted string.")) | |
(defrecord ArrayMine [a] | |
ArrayPlus | |
(join [am] (join am "")) | |
(join [am sep] (join am sep "%s")) | |
(join [am sep frmt] | |
(clojure.string/join sep (map #(format frmt %) a)))) | |
; ex. 37: | |
(def rooms (ArrayMine. [3 4 6])) | |
(str "We have " (join rooms ", " "%d bed") " rooms available.") | |
; ex. 38: | |
; Ruby modules are analagous to Clojure namespaces | |
(ns ex.watchful-saint-agnes) | |
(def ToothlessManWithFork ["man" "fork" "exposed gums"]) | |
(defrecord FatWaxyChild []) | |
(def timid-foxfaced-girl {"please" "i want an acorn please"}) | |
; ex. 39: | |
; this is another thing in Ruby that you can't really do in Clojure, | |
; or at least it's not idiomatic. You can access all of the above objects | |
; from a different namespace as ex.watchful-saint-agnes/FatWaxyChild, | |
; ex.watchful-saint-agnes/timid-foxfaced-girl, etc. The idea of "copying" them | |
; over into a new "class" doesn't really make sense in Clojure's functional | |
; style. | |
; ex. 40: | |
(defrecord LotteryTicket [picks purchased]) | |
(defn new-lottery-ticket [& picks] | |
(cond | |
(not= (count picks) 3) | |
(throw (IllegalArgumentException. "three numbers must be picked")) | |
(not= (count (distinct picks)) 3) | |
(throw (IllegalArgumentException. | |
"the three picks must be different numbers")) | |
(not-every? #(some #{%} (range 1 26)) picks) | |
(throw (IllegalArgumentException. | |
"the three picks must be numbers between 1 and 25"))) | |
(LotteryTicket. picks (java.util.Date.))) | |
; ex. 41: | |
; no need to do this in Clojure. Records automatically give you a standard | |
; way to "get" fields. Records function just like maps, which makes getting | |
; the fields as easy as getting a value from a map (see next example) | |
; ex. 42: | |
(def ticket (apply new-lottery-ticket (repeatedly 3 #(rand-nth (range 1 26))))) | |
(:picks ticket) | |
; ex. 43: | |
; unlike in Ruby, this works by default | |
(assoc ticket :picks [2 6 19]) | |
; ex. 44: | |
; in Clojure it would make more sense for this to be an ordinary function, | |
; not a class method belonging to the LotteryTicket "class" | |
(defn random-lottery-ticket [] | |
(apply new-lottery-ticket (repeatedly 3 #(rand-nth (range 1 26))))) | |
; results in an IllegalArgumentException if not all 3 numbers are unique | |
; ex. 45: | |
(defn random-lottery-ticket [] | |
(try (apply new-lottery-ticket (repeatedly 3 #(rand-nth (range 1 26)))) | |
(catch IllegalArgumentException e (random-lottery-ticket)))) | |
; recursively calls itself until it returns a valid lottery ticket (3 unique #'s) | |
; ex. 46: | |
; rather than making this all part of a "LotteryDraw class," it would be more | |
; idiomatic in Clojure to include all of these as top-level objects and functions | |
; in a namespace called, say, (ns ex.lottery-draw) | |
(def tickets (atom {})) | |
(defn buy [customer & tickets] | |
(swap! tickets #(merge-with concat % {customer tickets}))) | |
; ex. 47: | |
(buy "Yal-dal-rip-sip" | |
(new-lottery-ticket 12 6 19) | |
(new-lottery-ticket 5 1 3) | |
(new-lottery-ticket 24 6 8)) | |
; ex. 48: | |
(defprotocol Scoring | |
(score [ticket final] "Counts the number of correct numbers on the ticket.")) | |
(extend-type LotteryTicket | |
Scoring | |
(score [ticket final] | |
(count (clojure.set/intersection (set (:picks ticket)) (set (:picks final)))))) | |
; ex. 49: | |
(defn play [] | |
"Returns a hash of each winner to a list of their winning tickets, in the | |
form {winner ([ticket1 score1] [ticket2 score2])}." | |
(let [winning-numbers (random-lottery-ticket)] | |
(into {} | |
(for [[plyr tkts] @tickets | |
:when (some #(> (score % winning-numbers) 0) tkts)] | |
[plyr (for [tkt tkts | |
:let [score (score tkt winning-numbers)] | |
:when (> score 0)] | |
[tkt score])])))) | |
; ex. 50: | |
; not relevant to our Clojure translation of ex. 48. | |
; the "||=" syntax here is a Ruby trick used to "initialize" an entry | |
; in a map to an empty array [] if there is not already a key with a certain | |
; name in said map. This is not needed in Clojure, as you can just do, e.g., | |
; (merge-with f map {key val}) and if the map already contains the key, it will | |
; "update" it by calling the function on the existing value, otherwise it will | |
; "create" the {key val} entry you are merging in | |
; ex. 51: | |
; see above. Re: this particular example of conditional assignment | |
; (winners[buyer] = winners[buyer] || []), Clojure essentially has this | |
; "built into" its implentation of hashmaps. If you try to look up a key | |
; in a map that doesn't contain said key, you conveniently get nil. | |
; ex. 51-1/2: | |
; (this is an irb example that could just as easily be a standalone script) | |
(doseq [[winner tickets] (play)] | |
(println winner "won on" (count tickets) "ticket(s)!") | |
(doseq [[ticket score] tickets] | |
(println (str "\t" (clojure.string/join ", " (:picks ticket)) ": " score)))) | |
; exs. 52-53: | |
; not applicable to Clojure, wherein records work just like hash-maps | |
; ex. 54: | |
(defprotocol Judging | |
(the-winner [contest name])) | |
(defrecord SkatingContest [winner] | |
Judging | |
(the-winner [_ name] | |
(when-not (string? name) | |
(throw (IllegalArgumentException. "The winner's name must be a String, | |
not a math problem or a list of names or any of that business."))) | |
(SkatingContest. name))) | |
; example usage: | |
(def contest (SkatingContest. nil)) | |
(the-winner contest "Dave") ;=> (SkatingContest. "Dave") | |
; not exactly something you would do in Clojure, but it's possible! | |
; ex. 55: | |
(def NOTES [:Ab :A :Bb :B :C :Db :D :Eb :E :F :Gb :G]) | |
(defrecord AnimalLottoTicket [picks purchased]) | |
(defn new-ticket [note1 note2 note3 :as notes] | |
(when-not (distinct? notes) | |
(throw (IllegalArgumentException. "the three picks must be different notes"))) | |
(letfn [(valid-note? [x] (some #{x} NOTES))] | |
(when-not (every? valid-note? notes) | |
(throw (IllegalArgumentException. | |
"the three picks must be notes in the chromatic scale.")))) | |
(AnimalLottoTicket. notes (java.util.Date.))) | |
(defn random-ticket [] | |
(try (apply new-ticket (repeatedly 3 #(rand-nth NOTES))) | |
(catch IllegalArgumentException e (random-ticket)))) | |
(defprotocol Scoring | |
(score [ticket final] "Counts the number of correct numbers on the ticket.")) | |
(extend-type AnimalLotteryTicket | |
Scoring | |
(score [ticket final] | |
(count (clojure.set/intersection (set (:picks ticket)) (set (:picks final)))))) | |
; ex. 56: | |
; references the MindReader "read" method from ex. 13. In this Ruby example, | |
; _why refers to "self.read" within a module, with the intent of mixing the | |
; module into an existing class that contains a "read" method, such as the | |
; MindReader class from before. To emulate this in Clojure, we can create an | |
; ordinary function that takes a "this" argument (a record). See the next | |
; example for how to "mix in" this function to our existing MindReader record. | |
(require '[endertromb.core :as endertromb]) | |
(defn scan-for-a-wish [this] | |
(when-let [wish ((comp first filter) #(= (subs % 0 6) "wish: ") (read this))] | |
(clojure.string/replace wish "wish: " ""))) | |
; ex. 57: | |
; there is actually no need to "mix in" our scan-for-a-wish method. As an ordinary | |
; method, it can be used by (or rather, on) any record that implements a protocol | |
; that has a "read" method. If you pass in a (MindReader.) record to the | |
; scan-for-a-wish method, it will replace the "this" in (read this), and the | |
; correct "read" method will be dispatched. | |
; ex. 58: | |
(def reader (MindReader. (endertromb/scan-for-sentience))) | |
(def wisher (WishMaker. (rand-int 6))) | |
; this example doesn't really work within Clojure's functional programming | |
; paradigm. To be fair, it isn't properly explained how the Ruby example's | |
; infinite loop goes on to the next wish after the last one has been granted. I | |
; assume the WishMaker's "grant" method would destructively delete the wishful | |
; thought from the queue of thoughts read by the MindReader, so that on the next | |
; iteration of the loop, the "scan_for_a_wish" method will find a new wish. If | |
; this were an actual program in Clojure, scanning thoughts for wishes in | |
; real-time, we would probably want to use a ref or an atom to represent a (lazy?) | |
; sequence of thoughts, and have an infinite loop that checks the first thought | |
; and either grants it (if it starts with "wish: ") or discards it, updating the | |
; ref or atom's state with each iteration of the loop |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment