io.aviso.toolchest.macros
Generally useful macros for prettier and more expressive code.
cond-let
macro
(cond-let & forms)
A merging of cond and let. Each term is either a vector (in which case, it acts like let) or a condition expression followed by the value for that expression. An empty cond-let returns nil.
cond-let makes it possible to create readable code that doesn’t end up nesting ;endlessly.
A typical example is where several steps must occur, perhaps reading data from an external datastore and making a few consistency checks:
(defn apply-payment [db-conn order-id user-id payment-info]
(cond-let
[order (find-order-by-id db-conn order-id)]
(nil? order)
not-found-response
(not (owned-by? order user-id))
forbidden-response
[existing-payment (find-payment db-conn (:payment-id order))]
(some? existing-payment)
already-payed-response
:else
(do
(attach-payment db-conn order payment-info)
updated-response)))
consume
macro
(consume coll bindings & body)
Consume is used to break apart a collection into individual terms.
The format is:
(consume coll [symbol predicate arity ...] body)
The symbol is assigned a value by extracting zero or more values from the collection that match the predicate. The predicate is passed a single element from the collection and should return a truth value.
The arity is a keyword that identifies how many values are taken from the collection.
- :one
- The first value in the collection must match the predicate, or an exception is thrown. The value is assigned to the symbol. The literal value 1 may be used instead of :one.
- :?
- Matches 0 or 1 values from the collection. If the first value does not match the predicate, then nil is assigned to the symbol.
- :*
- Zero or more values from the collection are assigned to the symbol. The symbol may be assigned an empty collection.
- :+
- Matches one or more values; an exception is thrown if there are no matches.
The symbol/pred/arity triplet can be followed by additional triplets.
Although the above description discusses triplets, there are two special predicate values that are used as just a pair (symbol followed by special predicate), with no arity.
- :&
- Used to indicate consumption of all remaining values, if any, from the collection. It is not followed by an arity, and must be the final term in the bindings vector.
- :+
- Used to consume a single value always (throwing an exception if the collection is empty).
consume expands into a let form, so the symbol in each triplet may be a destructuring form.
As an example, a function that expects an optional map followed by at least one string, followed by any number of vectors:
(defn example
{:arglists '([options strings... & vectors] [strings... & vectors])}
[& args]
(consume args
[options map? :? ; nil or a map
strings string? :+ ; seq of strings, at least one
vectors :&] ; remaining arguments, may be empty
;; Use options, strings, vectors here
...))