Ep 018: Did I Work Late on Tuesday?
► Play EpisodeChristoph 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:
filter
out irrelevant entries thenreduce
on just those
- New 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)
- Just take entry:
- New question: how many minutes did I work on the weekend?
- New predicate:
(weekend? entry)
- Usage:
(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
partial
. - Now the
weekend?
function is a simpleor
of calls today-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" 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
:date
key, so the sameday-of-week?
predicate works on both.
Related episodes:
Clojure in this episode:
filter
reduce
partial
or
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))
)