Christoph wants to teach filter some vocabulary.
Continuing our discussion of rummaging through the big bag of data.Mental shift for solving the problem:Prior thinking: one function that has to look at all entriesNew thinking: filter out irrelevant entries then reduce on just thoseNew mentality emphasizes the problem of "picking" over "combining". Once you have the right set of entries, the reduce becomes trivial.Idea: build up a "vocabulary" of picking."You build up the language to the level you want to use it at."A "predicate" is simply a function that gives you a truth value.We want to create a set of predicates to use with filter.By convention, predicates in Clojure end with ?. Eg. some?, contains?, every?First predicate to create: (spans-midnight? start-timestamp end-timestamp)Problem: using it is verbose: (filter #(spans-midnight? (:start %) (:end %)) entries)Better idea: have the predicate take an entry.The predicate should speak at the level of the items for filter.Just take entry: (spans-midnight? entry)Usage: (filter spans-midnight? entries)New question: how many minutes did I work on the weekend?New predicate: (weekend? entry)Usage: (filter weekend? entries)"My time in Clojure makes me look at big, long functions and wonder if they should be broken into smaller pieces."Simplify implementation of weekend? with a simpler predicate: (day-of-week? weekday entry)Order matters: put weekday first for partial.Now the weekend? function is a simple or of calls to day-of-week?Even better: use an "extractor" function (day-of-week entry) that returns the day.Useful for day-of-week? but also for any other logic that needs to pull out the day.An "extractor" provides a useful view of the data.Now a weekday? predicate becomes trivial: (not (weekend? entry))Key idea: the use of language mirrors how we talk about it.Not just about decomposition, but about how it reads linguistically.Can make a predicate for any day of the week with: (partial day-of-week? :sunday), etc.Use like so: (filter (partial day-of-week? :sunday) entries)"Partial to parameterize a predicate." (Say that three times fast.)New question: did I work a long day on Tuesday?Won't work to write a predicate at the "entry" levelNeed a new "day" levelOnce again, the language hints at the level of abstraction.Idea: function that "uplevels" by taking a list of entries and producing a list of daysPredicates can work at both levels if entry and day have some consistent structure.The "structure" (or "data shape") is a consistent use of keys and key paths between abstractions. It is not a "base class".Eg.: both entry and day have a :date key, so the same day-of-week? predicate works on both.015: Finding the Time016: When 8 - 1 = 6017: Data, at Your ServicefilterreducepartialorCode sample from this episode:
(ns time.week-04
(:require
[time.week-03 :as week-03]
[java-time :as jt]))
; Helper for loading up time entries
(defn log-times
[filename]
(->> (week-03/lines filename)
(week-03/times)))
; Extractors
(defn day-of-week
[entry]
(jt/day-of-week (-> entry :date)))
; Predicates
(defn spans-midnight?
[entry]
(not= (jt/local-date (:start entry)) (jt/local-date (:end entry))))
(defn day-of-week?
[day entry]
(= (day-of-week entry) (jt/day-of-week day)))
(defn weekend?
[entry]
(or (day-of-week? :saturday entry)
(day-of-week? :sunday entry)))
(defn weekday?
[entry]
(not (weekend? entry)))
; Aggregations
(defn total-minutes
[entries]
(->> entries
(map :minutes)
(reduce +)))
(comment
(->> (log-times "time-log.txt")
(filter spans-midnight?))
(->> (log-times "time-log.txt")
(filter (partial day-of-week? :wednesday)))
(->> (log-times "time-log.txt")
(filter weekend?))
(->> (log-times "time-log.txt")
(filter weekday?)
(total-minutes))
)