forked from francoisdevlin/Full-Disclojure
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Sean
committed
Jan 11, 2010
1 parent
f3e4261
commit ea8fd7d
Showing
4 changed files
with
146 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
#Point Free Clojure | ||
|
||
Last time we used the threading macros to reduce code size an abstract away list construction. In this | ||
episode I'd like to discuss a second style for combining code, point free. Point free code is a style that | ||
stresses partial application and functional composition. Let's start by reviewing these concepts in a simple | ||
fn. | ||
|
||
episode-006=>(defn quadratic | ||
[a b c x] | ||
(+ (* a x x) (* b x) c)) | ||
|
||
Here we have a fn for evaluating any quadratic equation at any point x. However, sometimes we want to turn this | ||
general tool into a specific parabola. This is where partial application comes in. Let's use the equation x^2+x+1 as an | ||
example. | ||
|
||
episode-006=>(def a-parabola (partial quadratic 1 1 1)) | ||
|
||
Note: Show examples | ||
|
||
Now, suppose we want add a coordinate transform to this. This happens all the time when you can't measure x directly, but | ||
you can measure some related value u instead. For our very simple example, we're going to use this transform | ||
|
||
episode-006=>(defn x-transform [u] (+ 1 u)) | ||
|
||
Mathematically, we want to do this | ||
|
||
episode-006=>(a-parabola (x-transform 0)) | ||
|
||
Notice that we have a chaining of fns calls here. The mathematical term for this is composition. Clojure provides a | ||
mechanism form doing this, the compose operator. | ||
|
||
episode-006=>(def transformed-parabola (comp a-parabola x-transform)) | ||
|
||
Note: Show examples | ||
|
||
Now, for these examples I've broken up the partial application from the functional composition into different | ||
steps. However, I commonly will mix the partial application and composition together, yielding something like | ||
this. | ||
|
||
episode-006=>(def transformed-parabola | ||
(comp | ||
(partial quadratic 1 1 1) | ||
(partial + 1))) | ||
|
||
You can also use point free style condense code like you would with the thread-last macro. Let's revisit our square fn. | ||
|
||
Note: Show square-free. | ||
|
||
Now, this form is a bit heavy and long to read. This leads some Clojurians to avoid point-free code. Others, from a | ||
Haskell background may wonder why Clojure doesn't have better reader support for composition and partial application. | ||
After all, Clojure claims to be a functional language, and as such these tools should be straightforward to code. One | ||
could experiment with extending the Clojure reader, but I have found a middle ground that I think works well. In my own | ||
libraries, I have the following two aliases sufficient: | ||
|
||
episode-006=>(def & comp) | ||
episode-006=>(def p partial) | ||
|
||
You'd be surprised how much easier a single character alias makes using a fn. For example, our square-free fn now looks | ||
like this: | ||
|
||
Note: Show other square-free. | ||
|
||
This is nicer than it was, but it is still a bit more work than the threading macro. However, we are only considering | ||
using our square-free fn on its own. The true value of point free style isn't apparent until you work in higher order | ||
fns. Suppose you wanted to map this squaring operation over a list of values | ||
|
||
Note: Show anon square free | ||
|
||
Notice how this fits into the mapping operation anonymously, and everything just works. This is the type of job where | ||
composition of fns "just works". map expects a closure, comp produces a closure. comp expects closures, partial | ||
produces closures. This is also very easy to grow organically. | ||
|
||
Note: Show working over a string. | ||
|
||
Suppose the input is a string, not a set of numbers. We just add the parsing steps, and it works. Suppose later we find | ||
out that we need to add a pre-filtering step to this data. Well, it's comp & partial to the rescue again. | ||
|
||
Note: Turn into nasty-huge expression | ||
|
||
You can also see that the indentation level of the form tells us something about the operation. I use this to quickly | ||
glance and determine what is going on in each stage of the overall process. I also use it a cue to figure out where one | ||
stage of the process ends, and another begins. | ||
|
||
So, that's how I use point free style in Clojure. It's a different way to organize your code, and works well for certain | ||
problems. It's not good for everything, especially macro calls & forms with intended side effects. But, for purely | ||
functional applications it's handy. I hope this helps you think about problems from a different angle. And on that | ||
note, I'm Sean Devlin, and this is Full Disclojure. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
(ns episode-006) | ||
|
||
(defn quadratic | ||
[a b c x] | ||
(+ (* a x x) (* b x) c)) | ||
|
||
(def a-parabola (partial quadratic 1 1 1)) | ||
|
||
(defn x-transform | ||
[u] | ||
(+ 1 u)) | ||
|
||
(def transformed-parabola | ||
(comp | ||
a-parabola | ||
x-transform)) | ||
|
||
(def transformed-parabola | ||
(comp | ||
(partial quadratic 1 1 1) | ||
(partial + 1))) | ||
|
||
(def square-free | ||
(comp | ||
(partial reduce +) | ||
(partial filter odd?) | ||
(partial range 0) | ||
(partial * 2))) | ||
|
||
(def & comp) | ||
(def p partial) | ||
|
||
(def square-free | ||
(& | ||
(p reduce +) | ||
(p filter odd?) | ||
(p range 0) | ||
(p * 2))) | ||
|
||
(map (& (p reduce +) (p filter odd?) (p range 0) (p * 2)) [0 1 2 3 4 5]) | ||
|
||
(defn parse-int [s] (Integer/parseInt s)) | ||
|
||
((& (p map (& (p reduce +) | ||
(p filter odd?) | ||
(p range 0) | ||
(p * 2) | ||
parse-int | ||
str)) | ||
(p filter (& even? | ||
parse-int | ||
str))) | ||
"012345") |