Partials & Patterns Home Twitter schemer.in
In functional programming, a function can be applied to only some of the parameters. This operation will result in a new function that can be applied to the rest of the arguments to produce the final result. Partial application is an important function-building technique.
Clojure provides the
function for this purpose. An example of using
partial is given below:
(def f (partial list 1 2)) (f 10 20) ;;=> (1 2 10 20)
partial function assumes that the arguments are always applied in sequence. What if some of the arguments in-between
are unknown at the time of calling
partial? It should be possible to specify the unknown values as
"empty-slots", which can be filled-in later at the time of the final function application.
Let's assume that we have an operator that can construct such non-sequential partials for us.
We shall call this abstraction as
cut because it knows how to deal with segmented argument lists. The slot for
a missing argument will be represented by the pattern
First we shall look at a few examples of using
cut, then we will look at its actual implementation.
The following program shows that, at the basic level,
cut behaves exactly like
(def f (cut list 1 2)) (f 10 20) ;;=> (1 2 10 20)
The next program shows
cut with a single slot between two arguments. This means, the resulting function has to be
called with exactly one value, just enough to fill-in the slot.
(def f (cut list 1 <> 2)) (f 10) ;;=> (1 10 2)
The following examples show that slots can appear anywhere in the partial argument list:
(def f (cut list 1 <> 2 <> <> 3)) (f 10 20 30) ;;=> (1 10 2 20 30 3) (def f (cut list <> 1 <> 2 <> <> 3)) (f 10 20 30 40) ;;=> (10 1 20 2 30 40 3)
Finally, we introduce the rest pattern (
...) that will allow the partial function to accept an arbitrary number of
arguments at the tail of the argument-list:
(def f (cut list <> 1 <> 2 <> <> 3 ...)) (f 10 20 30 40 50 60 70 80) ;;=> (10 1 20 2 30 40 3 50 60 70 80)
cut operator is defined as a macro that walks down the partial argument list looking for slots.
It constructs a parameter list that contain entries for each slot. An argument list is also prepared with the partial arguments and
variables from the parameter list inserted in proper order. As the last step, a new function is created which will call the
partially-applied function with the fully-constructed argument list.
(defmacro cut [f arg & args] (let [arg-templ? (= arg '<>) a1 (if arg-templ? (gensym) arg)] ;; collect paremeter and argument lists in a single pass over `args` (loop [args args params (if arg-templ? [a1] ) final-args [a1]] (if (seq args) (let [a (first args)] (cond (= a '<>) (let [p (gensym)] (recur (rest args) (conj params p) (conj final-args p))) (= a '...) ; ignore rest of `args` (let [xs (gensym) params (conj params '& xs)] `(fn ~params (apply ~f ~@final-args ~xs))) :else (recur (rest args) params (conj final-args a)))) (if (seq params) `(fn ~params (~f ~@final-args)) `(fn [& ~(symbol "xs")] (apply ~f ~@final-args ~(symbol "xs"))))))))
cut operator was inspired by
a similar abstraction