(every? pos? [1 0 100])
;;=> false
user> (every? pos? [1 10 100])
;;=> true
As in the week before, I’ve been reading through some of the Domain Modeling posts on the PurelyFunctional.tv blog, notably:
domain model consists of information, operations, and invariants
some places to encode invariants: types, (property-based) tests, language features, data structures, runtime checks / assertions, documentation, proofs
write the model down to spot potential problems - the earlier the better
you have a model anyway, it’s just often ad hoc and partially written
Erik got a question if he applied domain modeling to a real project.
And he talked about a document signing example:
They could do a basic contract signing flow: Create a contract, enter email addresses, and it sends it to everyone to sign.
But customers wanted more control:
They wanted to say what order people had to sign in.
And they wanted other things besides signing, like a review step
⇒ hard to fit these new ideas into the simple model
The direct path was just more HTTP endpoints and SQL update statements and a bunch of conditionals
But thinking hard brought an idea of state machine (see also Code Hale’s article below)
especially the non-determinism allowing parties to sign in different order
The (state machine) idea was hard to sell, especially to engineers!
but just the process of modeling it with a state machine made them find some quick wins (like separating SQL updates into more atomic actions) which eventually simplified their model/code
every?
, not-every?
, not-any?
every? (hopefully obvious)
(every? pos? [1 0 100])
;;=> false
user> (every? pos? [1 10 100])
;;=> true
not-every? (read "at least one that is not")
it’s a complement to every?
(not-every? pos? [1 0 100])
;;=> true
(not-every? pos? [1 10 100])
;;=> false
not-any? (read "none")
Note: any? is a completely different predicate (it’s not really a collection function - it returns true for any argument, including nil)
(not-any? pos? [-1 0 -100])
;;=> true
user> (not-any? pos? [-1 0 100])
;;=> false
I needed to download product analytics data from Amplitude. Amplitude’s Export API generates a zip file that you can download and extract. Here’s a way to download such a file in Clojure:
(defn download-events! [output-file start end]
(let [{:strs [api-key secret-key]} @api-keys
response (http/get (format "https://amplitude.com/api/2/export?start=%s&end=%s" start end)
{:basic-auth [api-key secret-key]
:as :stream})]
(io/copy (:body response) (io/file output-file))))
(download-events! "all-events-in-january.zip" "20220101T00" "20220131T23")
Here’s a link that may help too: https://stackoverflow.com/questions/32742744/how-to-download-a-file-and-unzip-it-from-memory-in-clojure
I used this piece of code based on commons-compress library in the past when dealing with compressed files:
(defn- tar-gz-seq
"A seq of TarArchiveEntry instances on a TarArchiveInputStream."
[tis]
(when-let [item (.getNextTarEntry tis)]
(cons item (lazy-seq (tar-gz-seq tis)))))
(defn- unpack-archive-files
"Given a .tar.gz unpack it and process every entry via `unpack-file-fn`."
[compressed-input-stream unpack-file-fn]
(let [tis (TarArchiveInputStream. (GZIPInputStream. compressed-input-stream))
tar-seq (tar-gz-seq tis)]
(doseq [entry tar-seq]
(unpack-file-fn tis entry))))
Donut.system: your new favourite component library? (by Daniel Higginbotham)
Is there any performance penalty to using vars instead of function references?
only matters when they’re on a hot path or very fast. If they take microseconds or more to execute I wouldn’t be bothered by it
This is not really Clojure-specific, but I started reading through a bunch of posts about domain modeling by Eric Normand (a big FP & Clojure advocate). It starts with PF.tv newsletter # 446 and he’s still publishing new posts.
There’s a lot of great content and insights. My favorite one, so far, has been 447: Domain model fit where he talks about reference back to reality and, as an example, misuse of the Decorator pattern. It also links to two former episodes talking about the Decorator pattern in more detail:
We store user session data in Redis via nippy.
After upgrading the library to a newer version, we found a serialization issue in one particular use case:
clojure.lang.ExceptionInfo: Cannot thaw object: `taoensso.nippy/*thaw-serializable-allowlist*` check failed.
This is a security feature.
See `*thaw-serializable-allowlist*` docstring or https://github.com/ptaoussanis/nippy/issues/130 for details!
class-name: "org.joda.time.DateTime"
The solution was to, as they suggest, whitelist all the classes in the org.joda.time
package:
;; saving joda DateTime instances in the session requires custom allow list for nippy
;; see https://github.com/ptaoussanis/nippy/issues/130
(alter-var-root #'taoensso.nippy/*thaw-serializable-allowlist*
(fn [allowlist] (conj allowlist "org.joda.time.*")))
Notice that the default allowlist contains, for example, java.time
classes:
*
taoensso.nippy/*thaw-serializable-allowlist*
#{"java.lang.NullPointerException"
...
"java.time.LocalTime"
See the nippy issue for more details.
Some of the interesting stuff I did, learned or found in the past week.
For way too long, I’ve been frustrated by inability of the Cloudwatch Insights UI console to produce human-readable output of latest/earliest functions.
Consider this example: [1]
I wanted to migrate away from the existing Wordpress-based hosting for my curiosprogrammer.net blog for a long time yet I have not found time to do that until now. Today, I’m finally moving towards much simpler approach using Cryogen as the underlying blog engine.