compose
provides functions which make it more convenient to compose (sic!) functions in Clojure.
Clojure's 'threading macros' ->
and ->>
make it easy weave expressions together without creating
deeply nested expressions, allowing
user> (let [v 50]
(/ (+ (* v 2) 3) 4))
103/4
to become
user> (-> 50
(* 2)
(+ 3)
(/ 4))
103/4
which permits you to build more complex expressions while maintaining reasonable clarity of what is going on. That is very nice.
However, there are disadvantages to using this style too much: Big expressions are not very flexible because they leave the functionality of your program nested inside expressions where they cannot be reused.
The above very simple example consists of pieces of functionality: Multiplying by 2, Adding 3 and
dividing by 4. Each are expressed clearly and directly, using the expressions (* 2)
, (+ 3)
and
(/ 4)
, but not very flexibly: They cannot be easily reused nor can they be tested
separately, both quite significant disadvantages.
In many cases it is better to express functionalties directly, as functions. The definitions
(defn mult2 [v] (* v 2))
(defn add3 [v] (+ v 3))
(defn div4 [v] (/ v 4))
are the trivial improvement on the original example, allowing us to do
user> (let [f (comp div4 add3 mult2)]
(f 50))
103/4
instead. The definitions of mult3
and add3
can be made more succinct using 'point-free' form
using partial
thus avoiding the introductrion of a named variable:
(def mult2 (partial * 2))
(def add3 (partial + 3))
(def div4 #(/ % 4))
Notice that, since partial
will add arguments to the end, we must use different style of definition for
div4
. The above definitions are equivalent to the originals:
user> (let [f (comp div4 add3 mult2)]
(f 50))
103/4
The variant definition div4
seems quite arbitrary and depends on the specific functionality of
partial
. Clojure has operators which want their operand(s) at the start of the argument list, and
thus fit comfortably inside ->
forms, and operators which want their operand(s) at end end of the
end of the argument list, and thus fit comfortably inside ->>
forms. Clojure offers partial
which caters to the latter case, but lacks an equivalent operator catering to the former.
compose
strives to make using functions-as-values and composition easier by providing operators
which eliminate variations such as that between mult2
/add3
and div4
. It also provides
short-hand notation for some of the operators, allowing very succinct expression (at the price of
having to remember the notation!).
compose
provides, for example, raptial
which supplements partial
by returning a function
which a single value to the argument list before calling the wrapped function.
This allows these more consistent definitions
(def mult2 (partial * 2))
(def add3 (partial + 3))
(def div4 (raptial / 4))
For succinctness, compose
additionally provides the namespace io.simplect.compose.notation
which
adds short names (greek letters) for some the operators. Specifically, notation
defines π
(aka
GREEK SMALL LETTER PI
in Unicode) to mean partial
and Π
(GREEK CAPITAL LETTER PI
) to mean
raptial
. This allows the above definitions to be written equivalently as
(def mult2 (π * 2))
(def add3 (π + 3))
(def div4 (Π / 4))
notation
defines Γ
(GREEK CAPITAL LETTER GAMMA
) to mean rcomp
and γ
to (GREEK SMALL LETTER GAMMA
) to mean comp
, allowing us to define the original operation as
(def op (γ div4 add3 mult2))
or, equivalently and perhaps more intuitively, as
(def op (Γ mult2 add3 div4))
or perhaps simply as
(def op (Γ (π * 2) (π + 3) (Π / 4)))
although this form lumps the individual operations back together but illustrates the succinctness of
point-free function definitions and the operators of notation
.
In many situations using rcomp
thus applying functions in the order they appear makes the
expression easier to understand than using comp
which applies them in reverse order.
To close off the example the definitions
(def mult2 (Π * 2))
(def add3 (Π + 3))
(def div4 (Π / 4))
(def op (Γ mult2 add3 div4))
seem to strike an attractive balance, with clear code structure, the ability to test each part
separately and retaining the possibility of reusing each constituent part. (The definitions of
mult2
and add3
were adjusted to use Π
to eliminate arbitrary, unnecessary variation.) In this
case avoiding named arguments appears to help keep the code simple and easy to understand.
io.simplect.compose
strives to make it easier to program like this.
compose
makes no claims of originality. It merely assembles functionality the author has found
useful in his own quest towards a more functional programming style, some of which is lifted from
Clojure standard libraries (fmap
) or Funcool
cats (curry
). (Thanks!)
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close