From 5559fb3b3b1c56f7a249a96420b924c1681718b3 Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Wed, 18 Oct 2023 18:03:13 +0100 Subject: [PATCH 1/4] Added functions-generating-functions concept --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 51a74af2..5b2b1268 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ pom.xml.asc .clj-kondo .lsp *.calva +**/*/*.ipynb_checkpoints From e9c3ccec831c3936006c9e3ac7c75f611b84e901 Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Wed, 18 Oct 2023 18:05:37 +0100 Subject: [PATCH 2/4] Added functions-generating-functions concept --- .gitignore | 1 + .../functions-generating-functions/about.md | 3 + .../introduction.html | 349 ++++++++++++++++++ .../introduction.md | 138 +++++++ .../functions-generating-functions/links.json | 6 + 5 files changed, 497 insertions(+) create mode 100644 concepts/functions-generating-functions/about.md create mode 100644 concepts/functions-generating-functions/introduction.html create mode 100644 concepts/functions-generating-functions/introduction.md create mode 100644 concepts/functions-generating-functions/links.json diff --git a/.gitignore b/.gitignore index 5b2b1268..4c81933a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ pom.xml.asc .lsp *.calva **/*/*.ipynb_checkpoints +concepts/functions-generating-functions/introduction_files/ diff --git a/concepts/functions-generating-functions/about.md b/concepts/functions-generating-functions/about.md new file mode 100644 index 00000000..8c9ea926 --- /dev/null +++ b/concepts/functions-generating-functions/about.md @@ -0,0 +1,3 @@ +# About + +Being a functional language, functions in Clojure are treated as first-level citizens. They can be processed by other functions just like data is, and they can also be generated, just like data is. 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. \ No newline at end of file diff --git a/concepts/functions-generating-functions/introduction.html b/concepts/functions-generating-functions/introduction.html new file mode 100644 index 00000000..0bfe54af --- /dev/null +++ b/concepts/functions-generating-functions/introduction.html @@ -0,0 +1,349 @@ + + + + + + + + + +introduction + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+

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:

+
(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:

+
(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:

+
(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:

+
(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:

+
(f1 (f2 (... (fN x))))
+

By using comp, we can create a new function which performs this composition for us:

+
(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:

+
(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.

+
(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:

+
((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:

+
(def multiply-by-successive-factors (juxt (partial * 2) (partial * 3) (partial * 4) (partial * 5)))
+(multiply-by-successive-factors 10) ; => [20 30 40 50]
+
+
+ +
+ + +
+ + + + \ No newline at end of file diff --git a/concepts/functions-generating-functions/introduction.md b/concepts/functions-generating-functions/introduction.md new file mode 100644 index 00000000..bdaa7e22 --- /dev/null +++ b/concepts/functions-generating-functions/introduction.md @@ -0,0 +1,138 @@ +# 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 +(def multiply-by-successive-factors (juxt (partial * 2) (partial * 3) (partial * 4) (partial * 5))) +(multiply-by-successive-factors 10) ; => [20 30 40 50] +``` \ No newline at end of file diff --git a/concepts/functions-generating-functions/links.json b/concepts/functions-generating-functions/links.json new file mode 100644 index 00000000..666805b0 --- /dev/null +++ b/concepts/functions-generating-functions/links.json @@ -0,0 +1,6 @@ +[ + { + "url": "https://clojure.org/reference/reader#_literals", + "description": "Clojure reference: Literals" + } + ] \ No newline at end of file From 45f137152262a48eed207c8798581f7a9a6a4b83 Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Thu, 19 Oct 2023 14:54:05 +0100 Subject: [PATCH 3/4] Made examples more readable by introducing new lines. Added .meta/config.json and updated links.json --- .../.meta/config.json | 4 +++ .../introduction.md | 18 ++++++----- .../functions-generating-functions/links.json | 30 +++++++++++++++---- 3 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 concepts/functions-generating-functions/.meta/config.json diff --git a/concepts/functions-generating-functions/.meta/config.json b/concepts/functions-generating-functions/.meta/config.json new file mode 100644 index 00000000..91f270df --- /dev/null +++ b/concepts/functions-generating-functions/.meta/config.json @@ -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"] + } \ No newline at end of file diff --git a/concepts/functions-generating-functions/introduction.md b/concepts/functions-generating-functions/introduction.md index bdaa7e22..2064be99 100644 --- a/concepts/functions-generating-functions/introduction.md +++ b/concepts/functions-generating-functions/introduction.md @@ -9,7 +9,8 @@ In general, given an existing function `my-function`, with parameters `p1, p2, . 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)) +(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) ``` @@ -33,7 +34,8 @@ As a second example, we have a function `generic-greetings` that uses the name o 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?")) +(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? ``` @@ -54,7 +56,8 @@ In clojure, this is equivalent to doing: By using comp, we can create a new function which performs this composition for us: ```clojure -(def my-function-composition (comp f1 f2 ... fN)) +(def my-function-composition + (comp f1 f2 ... fN)) (my-function-composition x) ; equivalent to (f1 (f2 (... (fN x)))) @@ -64,7 +67,8 @@ As an example, let's say we want to sum a series of numbers and then multiply th ```clojure -(def six-times-result-sum (comp (partial * 6) +)) +(def six-times-result-sum + (comp (partial * 6) +)) (six-times-result-sum 3 2) ; = ((partial * 6) (+ 3 2)) ; = (* 6 (+ 3 2)) @@ -91,7 +95,8 @@ In order to see how this works, let us use a synthetic case where the function s ; => 6 ; We define a memoized function -(def my-memoized-fn (memoize my-time-consuming-fn) ) +(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 @@ -133,6 +138,5 @@ In order to see how this works, let us use a synthetic case where the function s As a simple example, we generate a function that computes the product of x by successive factors, from 2 to 5: ```clojure -(def multiply-by-successive-factors (juxt (partial * 2) (partial * 3) (partial * 4) (partial * 5))) -(multiply-by-successive-factors 10) ; => [20 30 40 50] +((juxt (partial * 2) (partial * 3) (partial * 4) (partial * 5)) 10) ; => [20 30 40 50] ``` \ No newline at end of file diff --git a/concepts/functions-generating-functions/links.json b/concepts/functions-generating-functions/links.json index 666805b0..be49344d 100644 --- a/concepts/functions-generating-functions/links.json +++ b/concepts/functions-generating-functions/links.json @@ -1,6 +1,26 @@ [ - { - "url": "https://clojure.org/reference/reader#_literals", - "description": "Clojure reference: Literals" - } - ] \ No newline at end of file + { + "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" + } +] \ No newline at end of file From ecc02598e2db14bef7e0af3f2135887380f2c08d Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Fri, 20 Oct 2023 04:29:31 +0100 Subject: [PATCH 4/4] Deleted html file from functions-generating-functions concept, updated config.json, and made about.md have the same content as introduction.md --- .gitignore | 1 + .../functions-generating-functions/about.md | 141 ++++++- .../introduction.html | 349 ------------------ config.json | 5 + 4 files changed, 146 insertions(+), 350 deletions(-) delete mode 100644 concepts/functions-generating-functions/introduction.html diff --git a/.gitignore b/.gitignore index 4c81933a..a943c431 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ pom.xml.asc *.calva **/*/*.ipynb_checkpoints concepts/functions-generating-functions/introduction_files/ +concepts/functions-generating-functions/*.html diff --git a/concepts/functions-generating-functions/about.md b/concepts/functions-generating-functions/about.md index 8c9ea926..59fd05a8 100644 --- a/concepts/functions-generating-functions/about.md +++ b/concepts/functions-generating-functions/about.md @@ -1,3 +1,142 @@ # About -Being a functional language, functions in Clojure are treated as first-level citizens. They can be processed by other functions just like data is, and they can also be generated, just like data is. 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. \ No newline at end of file +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] +``` \ No newline at end of file diff --git a/concepts/functions-generating-functions/introduction.html b/concepts/functions-generating-functions/introduction.html deleted file mode 100644 index 0bfe54af..00000000 --- a/concepts/functions-generating-functions/introduction.html +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - - - - -introduction - - - - - - - - - - - - - - - - - - - -
- -
- - - -
-

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:

-
(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:

-
(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:

-
(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:

-
(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:

-
(f1 (f2 (... (fN x))))
-

By using comp, we can create a new function which performs this composition for us:

-
(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:

-
(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.

-
(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:

-
((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:

-
(def multiply-by-successive-factors (juxt (partial * 2) (partial * 3) (partial * 4) (partial * 5)))
-(multiply-by-successive-factors 10) ; => [20 30 40 50]
-
-
- -
- - -
- - - - \ No newline at end of file diff --git a/config.json b/config.json index c3590b17..698bace5 100644 --- a/config.json +++ b/config.json @@ -1087,6 +1087,11 @@ "uuid": "75d16185-4ca9-49d1-969f-3dd6fc377b96", "slug": "closures", "name": "Closures" + }, + { + "uuid": "9d9d7cf1-8504-4f7b-b2a0-1b3b638dc0d9", + "slug": "functions-generating-functions", + "name": "Functions generating functions" } ], "key_features": [