Joseph Wilk

Joseph Wilk

Things with code, creativity and computation.

Functions Explained Through Patterns

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
Returns a lazy seq of the elements of coll separated by sep.
Returns a stateful transducer when no collection is provided.
  • Examples showing application of the function:
1
2
(interpose 1.0 [0.3 0.4])
;;=> [0.3 1.0 0.4 1.0]

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

Comments