-
-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Concepts/functions generating functions (#589)
* Added functions-generating-functions concept * Added functions-generating-functions concept * Made examples more readable by introducing new lines. Added .meta/config.json and updated links.json * Deleted html file from functions-generating-functions concept, updated config.json, and made about.md have the same content as introduction.md
- Loading branch information
1 parent
d5bdff9
commit 3fe389c
Showing
6 changed files
with
322 additions
and
0 deletions.
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,4 @@ | ||
{ | ||
"blurb": "We explore here four important cases of high-order functions that are used to generate new functions: `partial`, `comp`, `memoize` and `juxt`", | ||
"authors": ["JaumeAmoresDS"] | ||
} |
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,142 @@ | ||
# About | ||
|
||
Being a functional language, functions in Clojure are first-class citizens. This means that they can be passed to and generated by other functions just like data. Clojure in particular comes with a rich set of high-order functions that derive new functions based on existing ones. We will explore here four important cases: `partial`, `comp`, `memoize` and `juxt`. These function-generating functions fall into a broader category of higher-order functions, such as `map`, `reduce`, `apply`, `complement`, to name a few, which operate on existing functions. | ||
|
||
## partial | ||
|
||
In general, given an existing function `my-function`, with parameters `p1, p2, ..., pN` we can fix some of its parameters `p1, p2, ..., pM` to have constant values `v1, v2, ..., vM`. We do so by applying `partial` as follows: `(partial my-function v1 v2 ... vM)`. | ||
|
||
The result is a new function which applies the original one `my-function` by fixing `p1=v1, p2=v2, ..., pM=vM`. Our new function takes as input the remaining parameters `pM+1, pM+2, ..., pN`, whose values have not been indicated when applying `partial`: | ||
|
||
```clojure | ||
(def my-new-function | ||
(partial my-function v1 v2 ... vM)) | ||
(my-new-function xM+1 xM+2 ... xN) | ||
; => equivalent to (my-function v1 v2 ... vM xM+1 xM+2 ... xN) | ||
``` | ||
|
||
As a simple example, let's define a function `inc-by-9` which increases by 9, a fixed amount, whatever we pass-in. We could implement such function as follows: | ||
|
||
```clojure | ||
(def inc-by-9 (partial + 9)) | ||
(inc-by-9 5) | ||
; => 14 | ||
``` | ||
|
||
As a second example, we have a function `generic-greetings` that uses the name of the person we wish to greet along with initial and final text messages: | ||
|
||
```clojure | ||
(defn generic-greetings | ||
[initial-text final-text name] | ||
(println (str initial-text name final-text))) | ||
``` | ||
|
||
And use `partial` to always use a specific greetings message: | ||
|
||
```clojure | ||
(def say-hello-and-how-are-you-doing | ||
(partial generic-greetings "Hello " ", how are you doing?")) | ||
(say-hello-and-how-are-you-doing "Mary") | ||
; => Hello Mary, how are you doing? | ||
``` | ||
|
||
## comp | ||
|
||
`comp` can be used to create a composition of any number of functions we want to compose. In general, composing `N` functions `f1, f2, ..., fN` means applying those functions in sequential order, passing the ouput of the previous function to the next one. In mathematical notation this is expressed as: | ||
|
||
``` | ||
f1 (f2 (... fN(x))) | ||
``` | ||
|
||
In clojure, this is equivalent to doing: | ||
```clojure | ||
(f1 (f2 (... (fN x)))) | ||
``` | ||
|
||
By using comp, we can create a new function which performs this composition for us: | ||
|
||
```clojure | ||
(def my-function-composition | ||
(comp f1 f2 ... fN)) | ||
(my-function-composition x) | ||
; equivalent to | ||
(f1 (f2 (... (fN x)))) | ||
``` | ||
|
||
As an example, let's say we want to sum a series of numbers and then multiply the result by 6. We can do so as follows: | ||
|
||
|
||
```clojure | ||
(def six-times-result-sum | ||
(comp (partial * 6) +)) | ||
(six-times-result-sum 3 2) | ||
; = ((partial * 6) (+ 3 2)) | ||
; = (* 6 (+ 3 2)) | ||
; = 30 | ||
``` | ||
|
||
## memoize | ||
|
||
`memoize` allows to reuse previously computed results associated with a given input. Given the same input, the *memoized* function returns the same result without having to recompute it again. It takes advantage of the fact that `clojure` functions are, by default, *referentially transparent*: given the same input, they always produce the same output, which makes it possible to reuse previous computations with ease. | ||
|
||
In order to see how this works, let us use a synthetic case where the function sleeps for two seconds before producing the output, so we can easily compare the *computation time* before and after using `memoize`. | ||
|
||
```clojure | ||
(defn my-time-consuming-fn | ||
"Original, time-consuming function" | ||
[x] | ||
(Thread/sleep 2000) | ||
(* x 2) | ||
) | ||
|
||
; We measure the time it takes the original function | ||
(time (my-time-consuming-fn 3)) | ||
; => "Elapsed time: 2007.785622 msecs" | ||
; => 6 | ||
|
||
; We define a memoized function | ||
(def my-memoized-fn | ||
(memoize my-time-consuming-fn) ) | ||
|
||
; We call the memoized function and measure its execution time. | ||
; The first execution actually runs the function, taking | ||
; similar time as the original. | ||
(time (my-memoized-fn 3)) | ||
; => "Elapsed time: 2001.364052 msecs" | ||
; => 6 | ||
|
||
; Subsequent calls reuse the previous computation, taking less | ||
; time. Time is further reduced in additional calls. | ||
(time (my-memoized-fn 3)) | ||
; => "Elapsed time: 1.190291 msecs" | ||
; => 6 | ||
|
||
(time (my-memoized-fn 3)) | ||
; => "Elapsed time: 0.043701 msecs" | ||
; => 6 | ||
|
||
; Changing the input makes the function be executed, so that | ||
; we observe a similar computation time as the original | ||
(time (my-memoized-fn 4)) | ||
; => "Elapsed time: 2000.29306 msecs" | ||
; => 8 | ||
|
||
; Again, repeating the same input will make the | ||
; execution be skipped, and the associated output returned | ||
(time (my-memoized-fn 4)) | ||
; => "Elapsed time: 0.043701 msecs" | ||
; => 8 | ||
``` | ||
|
||
## juxt | ||
|
||
`juxt` generates a function that applies, in left to right order, the functions passed to it. The result is a vector with the individual results of each function as components of the vector: | ||
|
||
```clojure | ||
((juxt f g h) x) ; => [(f x) (g x) (h x)] | ||
``` | ||
|
||
As a simple example, we generate a function that computes the product of x by successive factors, from 2 to 5: | ||
```clojure | ||
((juxt (partial * 2) (partial * 3) (partial * 4) (partial * 5)) 10) ; => [20 30 40 50] | ||
``` |
142 changes: 142 additions & 0 deletions
142
concepts/functions-generating-functions/introduction.md
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,142 @@ | ||
# Introduction | ||
|
||
Being a functional language, functions in Clojure are first-class citizens. This means that they can be passed to and generated by other functions just like data. Clojure in particular comes with a rich set of high-order functions that derive new functions based on existing ones. We will explore here four important cases: `partial`, `comp`, `memoize` and `juxt`. These function-generating functions fall into a broader category of higher-order functions, such as `map`, `reduce`, `apply`, `complement`, to name a few, which operate on existing functions. | ||
|
||
## partial | ||
|
||
In general, given an existing function `my-function`, with parameters `p1, p2, ..., pN` we can fix some of its parameters `p1, p2, ..., pM` to have constant values `v1, v2, ..., vM`. We do so by applying `partial` as follows: `(partial my-function v1 v2 ... vM)`. | ||
|
||
The result is a new function which applies the original one `my-function` by fixing `p1=v1, p2=v2, ..., pM=vM`. Our new function takes as input the remaining parameters `pM+1, pM+2, ..., pN`, whose values have not been indicated when applying `partial`: | ||
|
||
```clojure | ||
(def my-new-function | ||
(partial my-function v1 v2 ... vM)) | ||
(my-new-function xM+1 xM+2 ... xN) | ||
; => equivalent to (my-function v1 v2 ... vM xM+1 xM+2 ... xN) | ||
``` | ||
|
||
As a simple example, let's define a function `inc-by-9` which increases by 9, a fixed amount, whatever we pass-in. We could implement such function as follows: | ||
|
||
```clojure | ||
(def inc-by-9 (partial + 9)) | ||
(inc-by-9 5) | ||
; => 14 | ||
``` | ||
|
||
As a second example, we have a function `generic-greetings` that uses the name of the person we wish to greet along with initial and final text messages: | ||
|
||
```clojure | ||
(defn generic-greetings | ||
[initial-text final-text name] | ||
(println (str initial-text name final-text))) | ||
``` | ||
|
||
And use `partial` to always use a specific greetings message: | ||
|
||
```clojure | ||
(def say-hello-and-how-are-you-doing | ||
(partial generic-greetings "Hello " ", how are you doing?")) | ||
(say-hello-and-how-are-you-doing "Mary") | ||
; => Hello Mary, how are you doing? | ||
``` | ||
|
||
## comp | ||
|
||
`comp` can be used to create a composition of any number of functions we want to compose. In general, composing `N` functions `f1, f2, ..., fN` means applying those functions in sequential order, passing the ouput of the previous function to the next one. In mathematical notation this is expressed as: | ||
|
||
``` | ||
f1 (f2 (... fN(x))) | ||
``` | ||
|
||
In clojure, this is equivalent to doing: | ||
```clojure | ||
(f1 (f2 (... (fN x)))) | ||
``` | ||
|
||
By using comp, we can create a new function which performs this composition for us: | ||
|
||
```clojure | ||
(def my-function-composition | ||
(comp f1 f2 ... fN)) | ||
(my-function-composition x) | ||
; equivalent to | ||
(f1 (f2 (... (fN x)))) | ||
``` | ||
|
||
As an example, let's say we want to sum a series of numbers and then multiply the result by 6. We can do so as follows: | ||
|
||
|
||
```clojure | ||
(def six-times-result-sum | ||
(comp (partial * 6) +)) | ||
(six-times-result-sum 3 2) | ||
; = ((partial * 6) (+ 3 2)) | ||
; = (* 6 (+ 3 2)) | ||
; = 30 | ||
``` | ||
|
||
## memoize | ||
|
||
`memoize` allows to reuse previously computed results associated with a given input. Given the same input, the *memoized* function returns the same result without having to recompute it again. It takes advantage of the fact that `clojure` functions are, by default, *referentially transparent*: given the same input, they always produce the same output, which makes it possible to reuse previous computations with ease. | ||
|
||
In order to see how this works, let us use a synthetic case where the function sleeps for two seconds before producing the output, so we can easily compare the *computation time* before and after using `memoize`. | ||
|
||
```clojure | ||
(defn my-time-consuming-fn | ||
"Original, time-consuming function" | ||
[x] | ||
(Thread/sleep 2000) | ||
(* x 2) | ||
) | ||
|
||
; We measure the time it takes the original function | ||
(time (my-time-consuming-fn 3)) | ||
; => "Elapsed time: 2007.785622 msecs" | ||
; => 6 | ||
|
||
; We define a memoized function | ||
(def my-memoized-fn | ||
(memoize my-time-consuming-fn) ) | ||
|
||
; We call the memoized function and measure its execution time. | ||
; The first execution actually runs the function, taking | ||
; similar time as the original. | ||
(time (my-memoized-fn 3)) | ||
; => "Elapsed time: 2001.364052 msecs" | ||
; => 6 | ||
|
||
; Subsequent calls reuse the previous computation, taking less | ||
; time. Time is further reduced in additional calls. | ||
(time (my-memoized-fn 3)) | ||
; => "Elapsed time: 1.190291 msecs" | ||
; => 6 | ||
|
||
(time (my-memoized-fn 3)) | ||
; => "Elapsed time: 0.043701 msecs" | ||
; => 6 | ||
|
||
; Changing the input makes the function be executed, so that | ||
; we observe a similar computation time as the original | ||
(time (my-memoized-fn 4)) | ||
; => "Elapsed time: 2000.29306 msecs" | ||
; => 8 | ||
|
||
; Again, repeating the same input will make the | ||
; execution be skipped, and the associated output returned | ||
(time (my-memoized-fn 4)) | ||
; => "Elapsed time: 0.043701 msecs" | ||
; => 8 | ||
``` | ||
|
||
## juxt | ||
|
||
`juxt` generates a function that applies, in left to right order, the functions passed to it. The result is a vector with the individual results of each function as components of the vector: | ||
|
||
```clojure | ||
((juxt f g h) x) ; => [(f x) (g x) (h x)] | ||
``` | ||
|
||
As a simple example, we generate a function that computes the product of x by successive factors, from 2 to 5: | ||
```clojure | ||
((juxt (partial * 2) (partial * 3) (partial * 4) (partial * 5)) 10) ; => [20 30 40 50] | ||
``` |
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,26 @@ | ||
[ | ||
{ | ||
"url": "https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/partial", | ||
"description": "Clojure API: partial" | ||
}, | ||
{ | ||
"url": "https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/comp", | ||
"description": "Clojure API: comp" | ||
}, | ||
{ | ||
"url": "https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/comp", | ||
"description": "Clojure API: comp" | ||
}, | ||
{ | ||
"url": "https://clojuredocs.org/clojure.core/memoize", | ||
"description": "Clojure docs: memoize" | ||
}, | ||
{ | ||
"url": "https://clojuredocs.org/clojure.core/juxt", | ||
"description": "Clojure docs: juxt" | ||
}, | ||
{ | ||
"url": "https://clojure.org/guides/higher_order_functions", | ||
"description": "Clojure guides: Higher Order Functions" | ||
} | ||
] |
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