Ep 018: Did I Work Late on Tuesday?
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 entries
- New thinking:
filterout irrelevant entries then
reduceon just those
- New mentality emphasizes the problem of “picking” over “combining”. Once you have the right set of entries, the
- 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
- By convention, predicates in Clojure end with
- 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
- Just take entry:
(filter spans-midnight? entries)
- Just take entry:
- New question: how many minutes did I work on the weekend?
- New predicate:
(filter weekend? entries)
- New predicate:
- “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
- Now the
weekend?function is a simple
orof calls to
- 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” level
- Need a new “day” level
- Once again, the language hints at the level of abstraction.
- Idea: function that “uplevels” by taking a list of entries and producing a list of days
- Predicates 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
:datekey, so the same
day-of-week?predicate works on both.
Clojure in this episode:
Code 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)) )