From 183cbc262813e6b4e55a8a1276996f9222a6bad1 Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Thu, 23 Nov 2023 04:22:04 +0000 Subject: [PATCH 1/6] Added map and reduce concepts --- concepts/map/.meta/config.json | 4 + concepts/map/about.md | 34 +++++++ concepts/map/introduction.md | 19 ++++ concepts/map/links.json | 10 +++ concepts/reduce/.meta/config.json | 4 + concepts/reduce/about.md | 142 ++++++++++++++++++++++++++++++ concepts/reduce/introduction.md | 63 +++++++++++++ concepts/reduce/links.json | 26 ++++++ config.json | 10 +++ 9 files changed, 312 insertions(+) create mode 100644 concepts/map/.meta/config.json create mode 100644 concepts/map/about.md create mode 100644 concepts/map/introduction.md create mode 100644 concepts/map/links.json create mode 100644 concepts/reduce/.meta/config.json create mode 100644 concepts/reduce/about.md create mode 100644 concepts/reduce/introduction.md create mode 100644 concepts/reduce/links.json diff --git a/concepts/map/.meta/config.json b/concepts/map/.meta/config.json new file mode 100644 index 00000000..846390a9 --- /dev/null +++ b/concepts/map/.meta/config.json @@ -0,0 +1,4 @@ +{ + "blurb": "We provide here a basic explanation about the map higher-order function and its most common usage.", + "authors": ["JaumeAmoresDS"] + } \ No newline at end of file diff --git a/concepts/map/about.md b/concepts/map/about.md new file mode 100644 index 00000000..e9c36167 --- /dev/null +++ b/concepts/map/about.md @@ -0,0 +1,34 @@ +# Introduction + +This concept provides a basic introduction to the `map` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `map` are deferred to a later concept. + +## Basic overview of map + +Let us first provide a simplified explanation that covers the most common usage of `map`, and briefly explain later more elaborate usage forms. + +In its most basic form, `map` accepts a previously defined function `f` as an argument, and a sequence of elements `s`, and applies `f` to each element of the sequence. As a result, it returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: + +```clojure +(map inc [1 2 3]) ; => '(2 3 4) +``` + +The previous example applies the clojure function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result of this application as a list `'(2 3 4)`, where each element is the result of applying `inc` to the corresponding element of [1 2 3]. + +Let's see another example where we greet a given person with the message "Welcome": + +```clojure +(defn say-welcome + [name] + (str "Welcome " name "!")) + +(map say-welcome ["Chris" "Jane" "Peter"]) ; => '("Welcome Chris!" "Welcome Jane!" "Welcome Peter!") +``` + +## Returning a lazy sequence + +Previously we provided a simplified explanation of how `map` operates. In reality, `map` returns a so-called *lazy* sequence. Although an explanation of lazy sequences goes beyond the scope of this introduction, it basically means that the elements of the resulting list are not computed until they are actually needed. In other words, it is not until we actually use elements of this list (e.g., to print them or retrieve their value) that the function `f` passed to `map` is applied, and the value of each element is computed. This is handy when `f` is computationally expensive, and, for instance, we only end-up needing to retrieve some of the elements of the resulting list later in the program. For all intents and purposes however, in its most basic form we can simplify and consider that `map` just returns a list as described above. + +## Using multiple collections + +`map` allows multiple collections to be passed as input. If the number of collections we pass is `n`, the function `f` will receive `n` elements, one for each collection in the list. The result is a list where the i-th element is obtained by applying `f` to the i-th element of each collection. + diff --git a/concepts/map/introduction.md b/concepts/map/introduction.md new file mode 100644 index 00000000..6b2d92d4 --- /dev/null +++ b/concepts/map/introduction.md @@ -0,0 +1,19 @@ +# Introduction + +`map` is a so-called *higher-order function*, which accepts a previously defined function `f` as an argument, and a sequence of elements `s`, and applies `f` to each element of the sequence. As a result, it returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: + +```clojure +(map inc [1 2 3]) ; => '(2 3 4) +``` + +The previous example applies the clojure function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result of this application as a list `'(2 3 4)`, where each element is the result of applying `inc` to the corresponding element of [1 2 3]. + +Let's see another example where we greet a given person with the message "Welcome": + +```clojure +(defn say-welcome + [name] + (str "Welcome " name "!")) + +(map say-welcome ["Chris" "Jane" "Peter"]) ; => '("Welcome Chris!" "Welcome Jane!" "Welcome Peter!") +``` diff --git a/concepts/map/links.json b/concepts/map/links.json new file mode 100644 index 00000000..9395ad83 --- /dev/null +++ b/concepts/map/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/map", + "description": "Clojure API: map" + }, + { + "url": "https://clojure.org/guides/higher_order_functions", + "description": "Clojure guides: Higher Order Functions" + } +] \ No newline at end of file diff --git a/concepts/reduce/.meta/config.json b/concepts/reduce/.meta/config.json new file mode 100644 index 00000000..4dd0dbc9 --- /dev/null +++ b/concepts/reduce/.meta/config.json @@ -0,0 +1,4 @@ +{ + "blurb": "We provide here a basic explanation about the reduce higher-order function and its most common usage.", + "authors": ["JaumeAmoresDS"] + } \ No newline at end of file diff --git a/concepts/reduce/about.md b/concepts/reduce/about.md new file mode 100644 index 00000000..59fd05a8 --- /dev/null +++ b/concepts/reduce/about.md @@ -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] +``` \ No newline at end of file diff --git a/concepts/reduce/introduction.md b/concepts/reduce/introduction.md new file mode 100644 index 00000000..b55b240d --- /dev/null +++ b/concepts/reduce/introduction.md @@ -0,0 +1,63 @@ +# Introduction + +This concept provides a basic introduction to the `reduce` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `reduce` are deferred to later concepts from our syllabus. + +## Basic overview of reduce + +`reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` takes applies the function `f` to the value `val` and to the first element `x_1` of the collection `coll`, `(f val x_1)`, then applies the function `f` to its own result and the second element `x_2` of the collection `coll`, `(f (f val x_1) x_2)`, then again to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using function `+`: + +```clojure +(reduce + 100 [1 2 3 4]) +;=> (+ 100 1) +;=> (+ 101 2) +;=> (+ 103 3) +;=> (+ 106 4) +;=> 110 +``` + +When called with only two arguments, `(reduce f coll)` applies the function `f` to the two elements, `x_1` and `x_2`, of the collection `coll`, then applies `f` to its own result and the third element `x_3` of `coll`, `(f (f x_1 x_2) x_3)`, then again to its own result and the fourth element of `coll`, and so on until all the elements of `coll` are used: + +```clojure +(reduce + [1 2 3 4]) +;=> (+ 1 2) +;=> (+ 3 3) +;=> (+ 6 4) +;=> 10 +``` + +In all these cases, the function `f` must accept at least two arguments, more if it accepts a variable number of arguments as in `+`. Let's see an example with a user-defined function: + +```clojure +(defn include-if-even + [coll x] + (if (= (mod x 2) 0) + (conj coll x) + coll)) + +(reduce include-if-even [] [1 2 3 4]) ;=> [2 4] +``` + +In the previous example, the function `include-if-even` accepts two arguments: a collection `coll` and a number `x`. It then checks if `x` module 2 is 0, in which case `x` is an even number, and it includes this number into the collection `coll` if that's the case, returning this collection. This function is then used to collect all the even numbers from a given collection: + +```clojure +(reduce include-if-even [] [1 2 3 4]) +;=> (include-if-even [] 1) +;=> (include-if-even [] 2) +;=> (include-if-even [2] 3) +;=> (include-if-even [2] 4) +;=> [2 4] +``` + +Note that in the previous example we must necessarily use three arguments in `reduce`, since our function needs a collection as its first argument. + +## Especial cases + +Especial cases arise when we use an empty collection or a collection with only one item: + +- If we apply `reduce` to an empty collection, `(reduce f val coll)` returns `(f val)`, and `(reduce f coll)` returns the result of applying `f` with no arguments. In this case, the function `f` must be able to use no arguments. +- If we apply `reduce` to a collection with only one item, `(reduce f val coll)` returns `(f val x_1)`, i.e., the result of applying `f` to `val` and the first element `x_1` of `coll`. If used with only two arguments, `(f coll)` returns the only element found in `coll`, and `f` is not called. + +## Using collections of functions + +`reduce` accepts any type of collection, including one that contains functions. In that case, we will typically use three arguments `(reduce f val coll)`, and the function `f` applies the first function `g_1` from the collection to `val`, then the second function `g_2` to the result of the previous application, `(f (f val g_1) g_2)` and so on until all the functions are applied. While this is an interesting use case, we leave a detailed explanation to more advanced concepts in our syllabus. + diff --git a/concepts/reduce/links.json b/concepts/reduce/links.json new file mode 100644 index 00000000..be49344d --- /dev/null +++ b/concepts/reduce/links.json @@ -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" + } +] \ No newline at end of file diff --git a/config.json b/config.json index 698bace5..e816d37b 100644 --- a/config.json +++ b/config.json @@ -1092,6 +1092,16 @@ "uuid": "9d9d7cf1-8504-4f7b-b2a0-1b3b638dc0d9", "slug": "functions-generating-functions", "name": "Functions generating functions" + }, + { + "uuid": "2369e86f-6921-49b6-807d-b1bdf9e37cd2", + "slug": "map", + "name": "map" + }, + { + "uuid": "1fcc926b-4e79-4377-874f-41626e59b87b", + "slug": "reduce", + "name": "reduce" } ], "key_features": [ From 60acd2df3a3248f58c65f7ef2a5e65cf28458a21 Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Thu, 23 Nov 2023 04:23:39 +0000 Subject: [PATCH 2/6] Made about.md be the same as introduction.md --- concepts/map/about.md | 17 +---- concepts/reduce/about.md | 153 ++++++++++----------------------------- 2 files changed, 38 insertions(+), 132 deletions(-) diff --git a/concepts/map/about.md b/concepts/map/about.md index e9c36167..6b2d92d4 100644 --- a/concepts/map/about.md +++ b/concepts/map/about.md @@ -1,12 +1,6 @@ # Introduction -This concept provides a basic introduction to the `map` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `map` are deferred to a later concept. - -## Basic overview of map - -Let us first provide a simplified explanation that covers the most common usage of `map`, and briefly explain later more elaborate usage forms. - -In its most basic form, `map` accepts a previously defined function `f` as an argument, and a sequence of elements `s`, and applies `f` to each element of the sequence. As a result, it returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: +`map` is a so-called *higher-order function*, which accepts a previously defined function `f` as an argument, and a sequence of elements `s`, and applies `f` to each element of the sequence. As a result, it returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: ```clojure (map inc [1 2 3]) ; => '(2 3 4) @@ -23,12 +17,3 @@ Let's see another example where we greet a given person with the message "Welcom (map say-welcome ["Chris" "Jane" "Peter"]) ; => '("Welcome Chris!" "Welcome Jane!" "Welcome Peter!") ``` - -## Returning a lazy sequence - -Previously we provided a simplified explanation of how `map` operates. In reality, `map` returns a so-called *lazy* sequence. Although an explanation of lazy sequences goes beyond the scope of this introduction, it basically means that the elements of the resulting list are not computed until they are actually needed. In other words, it is not until we actually use elements of this list (e.g., to print them or retrieve their value) that the function `f` passed to `map` is applied, and the value of each element is computed. This is handy when `f` is computationally expensive, and, for instance, we only end-up needing to retrieve some of the elements of the resulting list later in the program. For all intents and purposes however, in its most basic form we can simplify and consider that `map` just returns a list as described above. - -## Using multiple collections - -`map` allows multiple collections to be passed as input. If the number of collections we pass is `n`, the function `f` will receive `n` elements, one for each collection in the list. The result is a list where the i-th element is obtained by applying `f` to the i-th element of each collection. - diff --git a/concepts/reduce/about.md b/concepts/reduce/about.md index 59fd05a8..b55b240d 100644 --- a/concepts/reduce/about.md +++ b/concepts/reduce/about.md @@ -1,142 +1,63 @@ -# About +# 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. +This concept provides a basic introduction to the `reduce` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `reduce` are deferred to later concepts from our syllabus. -## partial +## Basic overview of reduce -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`: +`reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` takes applies the function `f` to the value `val` and to the first element `x_1` of the collection `coll`, `(f val x_1)`, then applies the function `f` to its own result and the second element `x_2` of the collection `coll`, `(f (f val x_1) x_2)`, then again to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using function `+`: ```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) +(reduce + 100 [1 2 3 4]) +;=> (+ 100 1) +;=> (+ 101 2) +;=> (+ 103 3) +;=> (+ 106 4) +;=> 110 ``` -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: +When called with only two arguments, `(reduce f coll)` applies the function `f` to the two elements, `x_1` and `x_2`, of the collection `coll`, then applies `f` to its own result and the third element `x_3` of `coll`, `(f (f x_1 x_2) x_3)`, then again to its own result and the fourth element of `coll`, and so on until all the elements of `coll` are used: ```clojure -(def inc-by-9 (partial + 9)) -(inc-by-9 5) -; => 14 +(reduce + [1 2 3 4]) +;=> (+ 1 2) +;=> (+ 3 3) +;=> (+ 6 4) +;=> 10 ``` -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: +In all these cases, the function `f` must accept at least two arguments, more if it accepts a variable number of arguments as in `+`. Let's see an example with a user-defined function: ```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: +(defn include-if-even + [coll x] + (if (= (mod x 2) 0) + (conj coll x) + coll)) -```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? +(reduce include-if-even [] [1 2 3 4]) ;=> [2 4] ``` -## 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 the previous example, the function `include-if-even` accepts two arguments: a collection `coll` and a number `x`. It then checks if `x` module 2 is 0, in which case `x` is an even number, and it includes this number into the collection `coll` if that's the case, returning this collection. This function is then used to collect all the even numbers from a given collection: -In clojure, this is equivalent to doing: ```clojure -(f1 (f2 (... (fN x)))) +(reduce include-if-even [] [1 2 3 4]) +;=> (include-if-even [] 1) +;=> (include-if-even [] 2) +;=> (include-if-even [2] 3) +;=> (include-if-even [2] 4) +;=> [2 4] ``` -By using comp, we can create a new function which performs this composition for us: +Note that in the previous example we must necessarily use three arguments in `reduce`, since our function needs a collection as its first argument. -```clojure -(def my-function-composition - (comp f1 f2 ... fN)) -(my-function-composition x) -; equivalent to -(f1 (f2 (... (fN x)))) -``` +## Especial cases -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: +Especial cases arise when we use an empty collection or a collection with only one item: +- If we apply `reduce` to an empty collection, `(reduce f val coll)` returns `(f val)`, and `(reduce f coll)` returns the result of applying `f` with no arguments. In this case, the function `f` must be able to use no arguments. +- If we apply `reduce` to a collection with only one item, `(reduce f val coll)` returns `(f val x_1)`, i.e., the result of applying `f` to `val` and the first element `x_1` of `coll`. If used with only two arguments, `(f coll)` returns the only element found in `coll`, and `f` is not called. -```clojure -(def six-times-result-sum - (comp (partial * 6) +)) -(six-times-result-sum 3 2) -; = ((partial * 6) (+ 3 2)) -; = (* 6 (+ 3 2)) -; = 30 -``` +## Using collections of functions -## memoize +`reduce` accepts any type of collection, including one that contains functions. In that case, we will typically use three arguments `(reduce f val coll)`, and the function `f` applies the first function `g_1` from the collection to `val`, then the second function `g_2` to the result of the previous application, `(f (f val g_1) g_2)` and so on until all the functions are applied. While this is an interesting use case, we leave a detailed explanation to more advanced concepts in our syllabus. -`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 From 67f7cdcec49cbfd73898d14e06a68cdb9d0e6c0b Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Thu, 23 Nov 2023 04:44:18 +0000 Subject: [PATCH 3/6] Improved readability of reduce introduction.md --- concepts/reduce/about.md | 12 +++++------- concepts/reduce/introduction.md | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/concepts/reduce/about.md b/concepts/reduce/about.md index b55b240d..b72a3707 100644 --- a/concepts/reduce/about.md +++ b/concepts/reduce/about.md @@ -4,7 +4,7 @@ This concept provides a basic introduction to the `reduce` higher-order function ## Basic overview of reduce -`reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` takes applies the function `f` to the value `val` and to the first element `x_1` of the collection `coll`, `(f val x_1)`, then applies the function `f` to its own result and the second element `x_2` of the collection `coll`, `(f (f val x_1) x_2)`, then again to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using function `+`: +The higher-order funcion `reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` applies the function `f` to `val` and the first element `x_1` of the collection `coll`: `(f val x_1)`. Then, it applies the function `f` to its own result and the second element `x_2` of the collection `coll`: `(f (f val x_1) x_2)`. Then again, it applies `f` to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using `+` as our function: ```clojure (reduce + 100 [1 2 3 4]) @@ -15,7 +15,7 @@ This concept provides a basic introduction to the `reduce` higher-order function ;=> 110 ``` -When called with only two arguments, `(reduce f coll)` applies the function `f` to the two elements, `x_1` and `x_2`, of the collection `coll`, then applies `f` to its own result and the third element `x_3` of `coll`, `(f (f x_1 x_2) x_3)`, then again to its own result and the fourth element of `coll`, and so on until all the elements of `coll` are used: +When called with only two arguments, `(reduce f coll)` applies the function `f` to the two first elements `x_1` and `x_2` of the collection `coll`, then applies `f` to its own result and the third element `x_3` of `coll`, then again to its own result and the fourth element of `coll`, and so on until all the elements of `coll` are used: ```clojure (reduce + [1 2 3 4]) @@ -25,7 +25,7 @@ When called with only two arguments, `(reduce f coll)` applies the function `f` ;=> 10 ``` -In all these cases, the function `f` must accept at least two arguments, more if it accepts a variable number of arguments as in `+`. Let's see an example with a user-defined function: +In all these cases, the function `f` must accept either two arguments or a variable number of arguments as happens in the previous case with function `+`. Let's see an example with a user-defined function: ```clojure (defn include-if-even @@ -33,11 +33,9 @@ In all these cases, the function `f` must accept at least two arguments, more if (if (= (mod x 2) 0) (conj coll x) coll)) - -(reduce include-if-even [] [1 2 3 4]) ;=> [2 4] ``` -In the previous example, the function `include-if-even` accepts two arguments: a collection `coll` and a number `x`. It then checks if `x` module 2 is 0, in which case `x` is an even number, and it includes this number into the collection `coll` if that's the case, returning this collection. This function is then used to collect all the even numbers from a given collection: +In the previous example, the function `include-if-even` accepts two arguments: a collection `coll` and a number `x`. It then checks if `x` module 2 is 0, in which case `x` is an even number. If that's the case, it includes this number into the collection `coll` and returns this collection. Otherwise, it returns the original collection. We can then apply `reduce` to collect all the even numbers from a given collection as follows: ```clojure (reduce include-if-even [] [1 2 3 4]) @@ -48,7 +46,7 @@ In the previous example, the function `include-if-even` accepts two arguments: a ;=> [2 4] ``` -Note that in the previous example we must necessarily use three arguments in `reduce`, since our function needs a collection as its first argument. +Note that in the previous example we must necessarily pass three arguments to `reduce`: our function `include-if-even`, an initial collection `[]`, and a collection of numbers like `[1 2 3 4]`. The initial collection `[]` is necessary due to the fact that `include-if-even` needs it as its first argument. ## Especial cases diff --git a/concepts/reduce/introduction.md b/concepts/reduce/introduction.md index b55b240d..b72a3707 100644 --- a/concepts/reduce/introduction.md +++ b/concepts/reduce/introduction.md @@ -4,7 +4,7 @@ This concept provides a basic introduction to the `reduce` higher-order function ## Basic overview of reduce -`reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` takes applies the function `f` to the value `val` and to the first element `x_1` of the collection `coll`, `(f val x_1)`, then applies the function `f` to its own result and the second element `x_2` of the collection `coll`, `(f (f val x_1) x_2)`, then again to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using function `+`: +The higher-order funcion `reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` applies the function `f` to `val` and the first element `x_1` of the collection `coll`: `(f val x_1)`. Then, it applies the function `f` to its own result and the second element `x_2` of the collection `coll`: `(f (f val x_1) x_2)`. Then again, it applies `f` to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using `+` as our function: ```clojure (reduce + 100 [1 2 3 4]) @@ -15,7 +15,7 @@ This concept provides a basic introduction to the `reduce` higher-order function ;=> 110 ``` -When called with only two arguments, `(reduce f coll)` applies the function `f` to the two elements, `x_1` and `x_2`, of the collection `coll`, then applies `f` to its own result and the third element `x_3` of `coll`, `(f (f x_1 x_2) x_3)`, then again to its own result and the fourth element of `coll`, and so on until all the elements of `coll` are used: +When called with only two arguments, `(reduce f coll)` applies the function `f` to the two first elements `x_1` and `x_2` of the collection `coll`, then applies `f` to its own result and the third element `x_3` of `coll`, then again to its own result and the fourth element of `coll`, and so on until all the elements of `coll` are used: ```clojure (reduce + [1 2 3 4]) @@ -25,7 +25,7 @@ When called with only two arguments, `(reduce f coll)` applies the function `f` ;=> 10 ``` -In all these cases, the function `f` must accept at least two arguments, more if it accepts a variable number of arguments as in `+`. Let's see an example with a user-defined function: +In all these cases, the function `f` must accept either two arguments or a variable number of arguments as happens in the previous case with function `+`. Let's see an example with a user-defined function: ```clojure (defn include-if-even @@ -33,11 +33,9 @@ In all these cases, the function `f` must accept at least two arguments, more if (if (= (mod x 2) 0) (conj coll x) coll)) - -(reduce include-if-even [] [1 2 3 4]) ;=> [2 4] ``` -In the previous example, the function `include-if-even` accepts two arguments: a collection `coll` and a number `x`. It then checks if `x` module 2 is 0, in which case `x` is an even number, and it includes this number into the collection `coll` if that's the case, returning this collection. This function is then used to collect all the even numbers from a given collection: +In the previous example, the function `include-if-even` accepts two arguments: a collection `coll` and a number `x`. It then checks if `x` module 2 is 0, in which case `x` is an even number. If that's the case, it includes this number into the collection `coll` and returns this collection. Otherwise, it returns the original collection. We can then apply `reduce` to collect all the even numbers from a given collection as follows: ```clojure (reduce include-if-even [] [1 2 3 4]) @@ -48,7 +46,7 @@ In the previous example, the function `include-if-even` accepts two arguments: a ;=> [2 4] ``` -Note that in the previous example we must necessarily use three arguments in `reduce`, since our function needs a collection as its first argument. +Note that in the previous example we must necessarily pass three arguments to `reduce`: our function `include-if-even`, an initial collection `[]`, and a collection of numbers like `[1 2 3 4]`. The initial collection `[]` is necessary due to the fact that `include-if-even` needs it as its first argument. ## Especial cases From 9faba518724c4c161d82b25866142d07542ae64d Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Thu, 23 Nov 2023 04:59:21 +0000 Subject: [PATCH 4/6] Improved readability of map --- concepts/map/about.md | 30 ++++++++++++++++++++++++++---- concepts/map/introduction.md | 30 ++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/concepts/map/about.md b/concepts/map/about.md index 6b2d92d4..5b9266e0 100644 --- a/concepts/map/about.md +++ b/concepts/map/about.md @@ -1,12 +1,18 @@ # Introduction -`map` is a so-called *higher-order function*, which accepts a previously defined function `f` as an argument, and a sequence of elements `s`, and applies `f` to each element of the sequence. As a result, it returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: +This concept provides a basic introduction to the `map` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `map` are deferred to a later concept. + +## Basic overview of map + +Let us first provide a simplified explanation that covers the most common usage of `map`, and briefly explain later more elaborate usage forms. + +In its most basic form, `map` accepts two arguments: a function `f` and a sequence of elements `s`. It then applies `f` to each element of the sequence, and returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: ```clojure -(map inc [1 2 3]) ; => '(2 3 4) +(map inc [1 2 3]) ; => (2 3 4) ``` -The previous example applies the clojure function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result of this application as a list `'(2 3 4)`, where each element is the result of applying `inc` to the corresponding element of [1 2 3]. +The previous example applies function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result of this application as a list `(2 3 4)`, where each element is the result of applying `inc` to the corresponding element of [1 2 3]. Let's see another example where we greet a given person with the message "Welcome": @@ -15,5 +21,21 @@ Let's see another example where we greet a given person with the message "Welcom [name] (str "Welcome " name "!")) -(map say-welcome ["Chris" "Jane" "Peter"]) ; => '("Welcome Chris!" "Welcome Jane!" "Welcome Peter!") +(map say-welcome ["Chris" "Jane" "Peter"]) ; => ("Welcome Chris!" "Welcome Jane!" "Welcome Peter!") ``` + +## Returning a lazy sequence + +Previously we provided a simplified explanation of how `map` operates. In reality, `map` returns a so-called *lazy* sequence. Although an explanation of lazy sequences goes beyond the scope of this introduction, it basically means that the elements of the resulting list are not generated until they are actually needed. In other words, it is not until we actually use elements of this list (e.g., to print them or retrieve their value) that the function `f` passed to `map` is applied to generate each element. This is advantageous when `f` is computationally expensive and we end-up only needing to retrieve some of the elements of the resulting list later in the program. + +## Using multiple collections + +`map` allows multiple collections to be passed as input. If the number of collections we pass is `n`, the function `f` will receive `n` elements, one for each collection in the list. The result is a list where the i-th element is obtained by applying `f` to the i-th element of each collection: + +```clojure +(def coll_a [a1 a2]) +(def coll_b [b1 b2]) +(def coll_c [c1 c2]) +(map f coll_a coll_b coll_c) ; => ((f a1 b1 c1) (f a2 b2 c2)) +``` + diff --git a/concepts/map/introduction.md b/concepts/map/introduction.md index 6b2d92d4..5b9266e0 100644 --- a/concepts/map/introduction.md +++ b/concepts/map/introduction.md @@ -1,12 +1,18 @@ # Introduction -`map` is a so-called *higher-order function*, which accepts a previously defined function `f` as an argument, and a sequence of elements `s`, and applies `f` to each element of the sequence. As a result, it returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: +This concept provides a basic introduction to the `map` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `map` are deferred to a later concept. + +## Basic overview of map + +Let us first provide a simplified explanation that covers the most common usage of `map`, and briefly explain later more elaborate usage forms. + +In its most basic form, `map` accepts two arguments: a function `f` and a sequence of elements `s`. It then applies `f` to each element of the sequence, and returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: ```clojure -(map inc [1 2 3]) ; => '(2 3 4) +(map inc [1 2 3]) ; => (2 3 4) ``` -The previous example applies the clojure function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result of this application as a list `'(2 3 4)`, where each element is the result of applying `inc` to the corresponding element of [1 2 3]. +The previous example applies function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result of this application as a list `(2 3 4)`, where each element is the result of applying `inc` to the corresponding element of [1 2 3]. Let's see another example where we greet a given person with the message "Welcome": @@ -15,5 +21,21 @@ Let's see another example where we greet a given person with the message "Welcom [name] (str "Welcome " name "!")) -(map say-welcome ["Chris" "Jane" "Peter"]) ; => '("Welcome Chris!" "Welcome Jane!" "Welcome Peter!") +(map say-welcome ["Chris" "Jane" "Peter"]) ; => ("Welcome Chris!" "Welcome Jane!" "Welcome Peter!") ``` + +## Returning a lazy sequence + +Previously we provided a simplified explanation of how `map` operates. In reality, `map` returns a so-called *lazy* sequence. Although an explanation of lazy sequences goes beyond the scope of this introduction, it basically means that the elements of the resulting list are not generated until they are actually needed. In other words, it is not until we actually use elements of this list (e.g., to print them or retrieve their value) that the function `f` passed to `map` is applied to generate each element. This is advantageous when `f` is computationally expensive and we end-up only needing to retrieve some of the elements of the resulting list later in the program. + +## Using multiple collections + +`map` allows multiple collections to be passed as input. If the number of collections we pass is `n`, the function `f` will receive `n` elements, one for each collection in the list. The result is a list where the i-th element is obtained by applying `f` to the i-th element of each collection: + +```clojure +(def coll_a [a1 a2]) +(def coll_b [b1 b2]) +(def coll_c [c1 c2]) +(map f coll_a coll_b coll_c) ; => ((f a1 b1 c1) (f a2 b2 c2)) +``` + From 6e8d9e10b4569eab1a0fabff1368152cc38c9090 Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Thu, 23 Nov 2023 05:06:26 +0000 Subject: [PATCH 5/6] Removed mentions to more advanced concepts --- concepts/map/about.md | 8 +------- concepts/map/introduction.md | 8 +------- concepts/reduce/about.md | 6 +----- concepts/reduce/introduction.md | 6 +----- 4 files changed, 4 insertions(+), 24 deletions(-) diff --git a/concepts/map/about.md b/concepts/map/about.md index 5b9266e0..b1b9a151 100644 --- a/concepts/map/about.md +++ b/concepts/map/about.md @@ -1,12 +1,6 @@ # Introduction -This concept provides a basic introduction to the `map` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `map` are deferred to a later concept. - -## Basic overview of map - -Let us first provide a simplified explanation that covers the most common usage of `map`, and briefly explain later more elaborate usage forms. - -In its most basic form, `map` accepts two arguments: a function `f` and a sequence of elements `s`. It then applies `f` to each element of the sequence, and returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: +In its most basic form, the higher-order function `map` accepts two arguments: a function `f` and a sequence of elements `s`. It then applies `f` to each element of the sequence, and returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: ```clojure (map inc [1 2 3]) ; => (2 3 4) diff --git a/concepts/map/introduction.md b/concepts/map/introduction.md index 5b9266e0..b1b9a151 100644 --- a/concepts/map/introduction.md +++ b/concepts/map/introduction.md @@ -1,12 +1,6 @@ # Introduction -This concept provides a basic introduction to the `map` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `map` are deferred to a later concept. - -## Basic overview of map - -Let us first provide a simplified explanation that covers the most common usage of `map`, and briefly explain later more elaborate usage forms. - -In its most basic form, `map` accepts two arguments: a function `f` and a sequence of elements `s`. It then applies `f` to each element of the sequence, and returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: +In its most basic form, the higher-order function `map` accepts two arguments: a function `f` and a sequence of elements `s`. It then applies `f` to each element of the sequence, and returns a list where the i-th element is the result of applying `f` to the i-th element of `s`: ```clojure (map inc [1 2 3]) ; => (2 3 4) diff --git a/concepts/reduce/about.md b/concepts/reduce/about.md index b72a3707..eafbf24b 100644 --- a/concepts/reduce/about.md +++ b/concepts/reduce/about.md @@ -1,9 +1,5 @@ # Introduction -This concept provides a basic introduction to the `reduce` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `reduce` are deferred to later concepts from our syllabus. - -## Basic overview of reduce - The higher-order funcion `reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` applies the function `f` to `val` and the first element `x_1` of the collection `coll`: `(f val x_1)`. Then, it applies the function `f` to its own result and the second element `x_2` of the collection `coll`: `(f (f val x_1) x_2)`. Then again, it applies `f` to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using `+` as our function: ```clojure @@ -57,5 +53,5 @@ Especial cases arise when we use an empty collection or a collection with only o ## Using collections of functions -`reduce` accepts any type of collection, including one that contains functions. In that case, we will typically use three arguments `(reduce f val coll)`, and the function `f` applies the first function `g_1` from the collection to `val`, then the second function `g_2` to the result of the previous application, `(f (f val g_1) g_2)` and so on until all the functions are applied. While this is an interesting use case, we leave a detailed explanation to more advanced concepts in our syllabus. +`reduce` accepts any type of collection, including one that contains functions. In that case, we will typically use three arguments `(reduce f val coll)`, and the function `f` applies the first function `g_1` from the collection to `val`, then the second function `g_2` to the result of the previous application, `(f (f val g_1) g_2)` and so on until all the functions are applied. diff --git a/concepts/reduce/introduction.md b/concepts/reduce/introduction.md index b72a3707..eafbf24b 100644 --- a/concepts/reduce/introduction.md +++ b/concepts/reduce/introduction.md @@ -1,9 +1,5 @@ # Introduction -This concept provides a basic introduction to the `reduce` higher-order function, where we explain its most common usage form. Advanced mechanisms for using `reduce` are deferred to later concepts from our syllabus. - -## Basic overview of reduce - The higher-order funcion `reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` applies the function `f` to `val` and the first element `x_1` of the collection `coll`: `(f val x_1)`. Then, it applies the function `f` to its own result and the second element `x_2` of the collection `coll`: `(f (f val x_1) x_2)`. Then again, it applies `f` to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using `+` as our function: ```clojure @@ -57,5 +53,5 @@ Especial cases arise when we use an empty collection or a collection with only o ## Using collections of functions -`reduce` accepts any type of collection, including one that contains functions. In that case, we will typically use three arguments `(reduce f val coll)`, and the function `f` applies the first function `g_1` from the collection to `val`, then the second function `g_2` to the result of the previous application, `(f (f val g_1) g_2)` and so on until all the functions are applied. While this is an interesting use case, we leave a detailed explanation to more advanced concepts in our syllabus. +`reduce` accepts any type of collection, including one that contains functions. In that case, we will typically use three arguments `(reduce f val coll)`, and the function `f` applies the first function `g_1` from the collection to `val`, then the second function `g_2` to the result of the previous application, `(f (f val g_1) g_2)` and so on until all the functions are applied. From f439364e8690b93e8715af38c3899b9464299449 Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Mon, 27 Nov 2023 08:34:44 +0000 Subject: [PATCH 6/6] Reduced contents of introduction.md --- concepts/map/introduction.md | 18 +----------------- concepts/reduce/introduction.md | 14 +------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/concepts/map/introduction.md b/concepts/map/introduction.md index b1b9a151..aec5b1ec 100644 --- a/concepts/map/introduction.md +++ b/concepts/map/introduction.md @@ -16,20 +16,4 @@ Let's see another example where we greet a given person with the message "Welcom (str "Welcome " name "!")) (map say-welcome ["Chris" "Jane" "Peter"]) ; => ("Welcome Chris!" "Welcome Jane!" "Welcome Peter!") -``` - -## Returning a lazy sequence - -Previously we provided a simplified explanation of how `map` operates. In reality, `map` returns a so-called *lazy* sequence. Although an explanation of lazy sequences goes beyond the scope of this introduction, it basically means that the elements of the resulting list are not generated until they are actually needed. In other words, it is not until we actually use elements of this list (e.g., to print them or retrieve their value) that the function `f` passed to `map` is applied to generate each element. This is advantageous when `f` is computationally expensive and we end-up only needing to retrieve some of the elements of the resulting list later in the program. - -## Using multiple collections - -`map` allows multiple collections to be passed as input. If the number of collections we pass is `n`, the function `f` will receive `n` elements, one for each collection in the list. The result is a list where the i-th element is obtained by applying `f` to the i-th element of each collection: - -```clojure -(def coll_a [a1 a2]) -(def coll_b [b1 b2]) -(def coll_c [c1 c2]) -(map f coll_a coll_b coll_c) ; => ((f a1 b1 c1) (f a2 b2 c2)) -``` - +``` \ No newline at end of file diff --git a/concepts/reduce/introduction.md b/concepts/reduce/introduction.md index eafbf24b..82318ea5 100644 --- a/concepts/reduce/introduction.md +++ b/concepts/reduce/introduction.md @@ -42,16 +42,4 @@ In the previous example, the function `include-if-even` accepts two arguments: a ;=> [2 4] ``` -Note that in the previous example we must necessarily pass three arguments to `reduce`: our function `include-if-even`, an initial collection `[]`, and a collection of numbers like `[1 2 3 4]`. The initial collection `[]` is necessary due to the fact that `include-if-even` needs it as its first argument. - -## Especial cases - -Especial cases arise when we use an empty collection or a collection with only one item: - -- If we apply `reduce` to an empty collection, `(reduce f val coll)` returns `(f val)`, and `(reduce f coll)` returns the result of applying `f` with no arguments. In this case, the function `f` must be able to use no arguments. -- If we apply `reduce` to a collection with only one item, `(reduce f val coll)` returns `(f val x_1)`, i.e., the result of applying `f` to `val` and the first element `x_1` of `coll`. If used with only two arguments, `(f coll)` returns the only element found in `coll`, and `f` is not called. - -## Using collections of functions - -`reduce` accepts any type of collection, including one that contains functions. In that case, we will typically use three arguments `(reduce f val coll)`, and the function `f` applies the first function `g_1` from the collection to `val`, then the second function `g_2` to the result of the previous application, `(f (f val g_1) g_2)` and so on until all the functions are applied. - +Note that in the previous example we must necessarily pass three arguments to `reduce`: our function `include-if-even`, an initial collection `[]`, and a collection of numbers like `[1 2 3 4]`. The initial collection `[]` is necessary due to the fact that `include-if-even` needs it as its first argument. \ No newline at end of file