Sveriges mest populära poddar

Functional Design in Clojure

Ep 015: Finding the Time

26 min • 8 februari 2019

Nate spends some time figuring out how to track his time.

  • New problem: Want to track time using a text file.
  • Text files are great:
    • Keyboard oriented: in terminal, in editor
    • Something you can put under revision control
  • Need: date and time range
  • Format of the time stamp should be convenient for us to read and edit.
  • Let the computer do the work to convert the convenient notation to something usable!
  • Format: Fri Feb 08 2019 11:30-13:45
  • One timestamp per line. Oh wait, we need a description. Six minutes in and we're already changing the requirements!
  • What are "literate" text files? "You can mix human words in amongst data the computer will use to make it more understandable for the humans."
  • How to find the times? Attempt to match the time format within each line.
  • "It's kind of like an inverse comment. Instead of every line being valid and you have to comment out lines you don't want, if it's in a known format, those are the lines we want and everything else is a comment."
  • Can use line-seq with clojure.java.io/reader. That uses lazy I/O.
  • Potential Issue: lazy I/O can defer I/O errors to when you're in the middle of processing the data.
  • Lazy I/O is less of an issue with files and more of an issue with network sockets.
  • Another approach: slurp in all the data at once and call split-lines
  • "Trade a little memory for some safety."
  • Clojure uses the seq abstraction whichever way you choose. It's Clojure's unifying abstraction.
  • "Even maps get squeezed into a seq if you push them hard enough."
  • Next step: figure out which lines are time entry lines and which lines are commentary
  • Use a regex to match against each string using Clojure's built-in #"..." form.
  • In other situations, you can use instaparse for grammar-based parsing.
  • Put timestamps on their own line
    • Easier to work with
    • Allows for more error detection (in the future)
  • Use capture groups and re-matches detect the match and extract the parts.
  • Use when-let to destructure and do something only if it matches
  • Better yet, make a function time-info that does the match and returns either nil or the string that matched.
  • For now, just start by using doseq to just go through all the lines and test our matching.
  • Making sense of the timestamp string is a whole new problem for next time.

Clojure in this episode:

  • nil
  • seq
  • line-seq
  • clojure.java.io/reader
  • slurp
  • clojure.string/split-lines
  • #"", re-matches
  • let, when-let
  • doseq

Related projects:

Code sample from this episode:

(def timestamp-re #"(\w+)\s(\w+)\s(\d+)\s(\d+)\s+(\d{2}):(\d{2})-(\d{2}):(\d{2})")

(with-open [rdr (clojure.java.io/reader "time-log.txt")]
  (doseq [line (line-seq rdr)]
    (when-let [[whole d m dt yr hs ms he me] (re-matches timestamp-re line)]
      (println whole))))
Förekommer på
00:00 -00:00