-
-
Notifications
You must be signed in to change notification settings - Fork 159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Concepts/functions generating functions #589
Merged
bobbicodes
merged 4 commits into
exercism:main
from
JaumeAmoresDS:concepts/functions-generating-functions
Oct 20, 2023
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
5559fb3
Added functions-generating-functions concept
JaumeAmoresDS e9c3cce
Added functions-generating-functions concept
JaumeAmoresDS 45f1371
Made examples more readable by introducing new lines. Added .meta/con…
JaumeAmoresDS ecc0259
Deleted html file from functions-generating-functions concept, update…
JaumeAmoresDS File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The about page should contain the same content as
introduction.md
(or more, but not less)