Sveriges mest populära poddar

Functional Design in Clojure

Ep 018: Did I Work Late on Tuesday?

29 min • 1 mars 2019

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: filter out irrelevant entries then reduce 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)
  • 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" 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 same day-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))
  )
Förekommer på
00:00 -00:00