Exploring patterns as a means of documenting Clojure functions to aid recall and understanding.
Whats the difference in Clojure between:
partition
and partition-all
?
interpose
and interleave
?
cons
and conj
?
Documenting Functions
All non-side effecting functions create or alter a pattern. To explain a function’s pattern we use a number of descriptions.
- A function signature:
(nthrest coll n)
- A textual description:
1 2 |
|
- Examples showing application of the function:
1 2 |
|
Exploration vs Recall
As someone with a brain far more orientated to visuals than text I struggled to remember and understand many Clojure functions: nthrest
, conj
cons
, etc.
The documentation of patterns in the Clojure documentation is all text. Even with the documentation brought into the editor I struggle. Example from Cider & Emacs:
Clojure has a strong focus on REPL driven development. If you don’t understand a function use an interactive REPL to explore examples.
Critically this favours discovery over recall. I can never remember the difference between conj
and cons
, but I can find out through the REPL.
To help aid memory and understanding I’ve turn the examples of the collection orientated functions in Clojure into visual patterns. I won’t try and make any general case on visuals vs text (Its a fuzzy research area: http://studiokayama.com/text-vs-visuals/).
Patterns for the visual brain
Applying functions to collections of data using: https://github.com/josephwilk/functions-as-patterns
(butlast coll)
Return a seq of all but the last item in coll, in linear time
butlast )
;;=>
(concat x y)
Returns a lazy seq representing the concatenation of the elements
in the supplied colls.
concat )
;;=>
(conj coll x) (conj coll x & xs)
conj[oin]. Returns a new collection with the xs
'added'. (conj nil item) returns (item). The 'addition' may
happen at different 'places' depending on the concrete type.
conj vector
conj )
;;=>
conj list
conj )
;;=>
(cons x seq)
Returns a new seq where x is the first element and seq is
the rest.
cons )
;;=>
(dedupe coll)
Returns a lazy sequence removing consecutive duplicates in coll.
Returns a transducer when no collection is provided.
dedupe )
;;=>
(distinct coll)
Returns a lazy sequence of the elements of coll with duplicates removed.
Returns a stateful transducer when no collection is provided.
distinct )
;;=>
(drop-last n coll)
Return a lazy sequence of all but the last n (default 1) items in coll
drop-last 2 )
;;=>
(flatten coll)
Takes any nested combination of sequential things (lists, vectors,
etc.) and returns their contents as a single, flat foldable
collection.
flatten )
;;=>
(interpose sep coll)
Returns a lazy seq of the elements of coll separated by sep.
Returns a stateful transducer when no collection is provided.
interpose )
;;=>
(interleave coll coll)
Returns a lazy seq of the first item in each coll, then the second etc.
interleave )
;;=>
(nthnext coll n)
Returns the nth next of coll, (seq coll) when n is 0.
nthnext 2)
;;=>
(nthrest coll n)
Returns the nth rest of coll, coll when n is 0.
nthrest 2 )
;;=>
(partition n coll)
Returns a lazy sequence of lists of n items each, at offsets step apart.
If step is not supplied, defaults to n, i.e. the partitions do not overlap.
If a pad collection is supplied, use its elements as necessary to complete
last partition upto n items. In case there are not enough padding elements,
return a partition with less than n items.
partition 3 )
;;=>
(partition-all n coll)
Returns a lazy sequence of lists like partition, but may include
partitions with fewer than n items at the end. Returns a stateful
transducer when no collection is provided.
partition-all 3 )
;;=>
(replace smap coll)
Given a map of replacement pairs and a vector/collection, returns a vector/seq
with any elements = a key in smap replaced with the corresponding val in smap.
Returns a transducer when no collection is provided.
replace [0 3 4] )
;;=>
(rest coll)
Returns a possibly empty seq of the items after the first. Calls seq on its
argument.
rest )
;;=>
(reverse coll)
Returns a seq of the items in coll in reverse order. Not lazy.
reverse )
;;=>
(shuffle coll)
Return a random permutation of coll.
shuffle )
;;=>
(sort coll)
Returns a sorted sequence of the items in coll. If no comparator is
supplied, uses compare. comparator must implement
java.util.Comparator. Guaranteed to be stable: equal elements will
not be reordered. If coll is a Java array, it will be modified. To
avoid this, sort a copy of the array.
sort )
;;=>
(take-nth n coll)
Returns a lazy seq of every nth item in coll.
take-nth 3 )
;;=>
(split-at n coll)
Returns a vector of [(take n coll) (drop n coll)]
split-at 2 )
;;=>
Conclusions
As someone who performs live coding to an audience I perhaps have a different value on recall vs exploration. Hundreds of eyes staring at you tends to have that effect.
While some examples are stronger through patterns than others, at least for myself the use of a visual aid as part of development and documentation is beneficial. Its the only way I can the remember the oddity of conj
.
Within my REPL interaction I use the functions-as-patterns toolkit, providing a higher level representation of the patterns and data. I can understand a drum pattern faster through colour than I can through a 1 & 0 data structure.
In creating the cheatsheet the value of the comparison of functions through patterns also became clear. I discovered almost identical functions such as nthnext
and nthrest
which only differed in a special case (with an empty sequence).
Problems of turning data into colour
While this visual cheatsheet is useful there are caveats:
Semantic’s of arguments
Its not always clear if an argument is an index or a value. If we look at the replace
function example:
(replace (take 5 (hues-of-color)) [0 3 4])
0,3 & 4 are references to indices within the first argument. Ideally it would be nice to replace those with the relevant colour.
However the functions-as-patterns library cannot tell these are not values, it assumes everything is a value. Hence you end up with [0 3 4]
drawn in shades of black:
Pattern of emptyness
I’ve not tried to visually represent, empty
or nil
. Some functions are defined by the difference in handling the empty case. The patterns might mis-lead you to think nthnext
and nthrest
are identical when they are not.
What type is a square?
Clojure has multiple types of sequences, char-arrays
, lists
, vectors
, lazy-seq
,etc. To keep the visual pattern simple I’ve not represented these types.
Functions applying functions
I’ve purposely skipped the map
/reduce
/remove
/filter
functions as they tend to mix two patterns together. That of the core function and the applied function. The value of the patterns gets lost.
Brains seek patterns.
Do you see a pattern in the randomness? A single example might reveal a false pattern. Many examples would be required to re-enforce the randomness of the resulting patterns. Example shuffle
.
Ordering
Colours don’t always imply a logical order. Example sort
.
Sources
Inspired by the work of Alex McLean (@yaxu) using visual patterns to explain the live coding language Tidal: Tidal Pattern Language for Live Coding of Music