Yet another variation:
(defn conjugate [f g & [g-inv]]
(comp (or g-inv g) f g))
(defn composite [f f-inv n x]
(nth (iterate (if (pos? n) f f-inv) x) (abs n)))
(defn rotate-left [xs]
(when (seq xs) (concat (rest xs) [(first xs)])))
(def rotate-right (conjugate rotate-left reverse))
(defn rotate [n xs]
(composite rotate-left rotate-right n xs))
This is intended to expose certain symmetries.
One of the basic ideas in mathematics is that an invertible
transformation T acts on elements by application, so that x becomes
T(x), and on functions by conjugation, so that f becomes T^(-1) . f .
T.
Another basic idea is that iterated compositions are like powers: f^n
is the n-fold composition of f. This is what iterate tries to capture.
This is meaningful for negative values of n when f is invertible. This
is what composite tries to capture.
If there were more direct support for doubly-infinite sequences (a fun
little programming exercise) then composite could return a
doubly-infinite sequence (much like iterate returns a singly-infinite
sequence), take would work with negative arguments, next would have a
counterpart prev, etc. Another fun exercise is to provide more direct
support for invertible functions. An invertible function can be
represented by a pair of functions. It implements IFn by applying the
first element of the pair. Inverting is just a matter of swapping the
elements.
Food for thought. :)
-Per