From 183cbc262813e6b4e55a8a1276996f9222a6bad1 Mon Sep 17 00:00:00 2001 From: Jaume Amores Date: Thu, 23 Nov 2023 04:22:04 +0000 Subject: [PATCH 01/11] 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 000000000..846390a94 --- /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 000000000..e9c361673 --- /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 000000000..6b2d92d47 --- /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 000000000..9395ad835 --- /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 000000000..4dd0dbc9f --- /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 000000000..59fd05a88 --- /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 000000000..b55b240d3 --- /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 000000000..be49344d7 --- /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 698bace5c..e816d37b0 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 02/11] 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 e9c361673..6b2d92d47 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 59fd05a88..b55b240d3 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 03/11] 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 b55b240d3..b72a3707c 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 b55b240d3..b72a3707c 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 04/11] 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 6b2d92d47..5b9266e02 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 6b2d92d47..5b9266e02 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 05/11] 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 5b9266e02..b1b9a1518 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 5b9266e02..b1b9a1518 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 b72a3707c..eafbf24bf 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 b72a3707c..eafbf24bf 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 06/11] 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 b1b9a1518..aec5b1ec5 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 eafbf24bf..82318ea5b 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 From c6588e7142a52e8e1a6de0627987d1a8b720dcc2 Mon Sep 17 00:00:00 2001 From: Jaume Amores <124528012+JaumeAmoresDS@users.noreply.github.com> Date: Tue, 1 Jul 2025 11:57:29 +0100 Subject: [PATCH 07/11] Apply suggestions from code review Co-authored-by: Anastasios Chatzialexiou <16361161+tasxatzial@users.noreply.github.com> --- concepts/map/.meta/config.json | 4 ++-- concepts/map/about.md | 19 +++++++++++++------ concepts/map/introduction.md | 7 ++++--- concepts/map/links.json | 10 +++------- concepts/reduce/.meta/config.json | 4 ++-- concepts/reduce/links.json | 26 +++----------------------- 6 files changed, 27 insertions(+), 43 deletions(-) diff --git a/concepts/map/.meta/config.json b/concepts/map/.meta/config.json index 846390a94..908de6fdc 100644 --- a/concepts/map/.meta/config.json +++ b/concepts/map/.meta/config.json @@ -1,4 +1,4 @@ { - "blurb": "We provide here a basic explanation about the map higher-order function and its most common usage.", + "blurb": "The map function applies a given function to each element of one or more collections.", "authors": ["JaumeAmoresDS"] - } \ No newline at end of file + } diff --git a/concepts/map/about.md b/concepts/map/about.md index b1b9a1518..2b8bad217 100644 --- a/concepts/map/about.md +++ b/concepts/map/about.md @@ -1,14 +1,15 @@ # Introduction -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`: +In its most basic form, the higher-order function `map` accepts two arguments: a function `f` and a collection `coll`. +It applies `f` to each element of `coll`, returning a list of the results in the same order. ```clojure (map inc [1 2 3]) ; => (2 3 4) ``` -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]. +The previous example applies the function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result as the 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": +Let's see another example where we greet someone with the message "Welcome": ```clojure (defn say-welcome @@ -20,11 +21,17 @@ Let's see another example where we greet a given person with the message "Welcom ## 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. +Previously we provided a simplified explanation of how `map` operates. +In reality, `map` does not return a list but a *lazy* sequence. +Although an explanation of lazy sequences is beyond the scope of this introduction, it essentially means that the elements of the resulting sequence are not generated until they are actually needed. +In other words, the function `f` passed to `map` is not applied until the elements of the resulting sequence are requested, for example, by printing them or retrieving their values. +This is advantageous when `f` is computationally expensive and only some elements are actually needed. -## Using multiple collections +## 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: +`map` allows multiple collections to be passed as input. +If the number of collections passed is `n`, the function `f` will receive `n` arguments, one from each collection. +The result is a sequence where the i-th element is obtained by applying `f` to the `n` elements at position i from each collection. ```clojure (def coll_a [a1 a2]) diff --git a/concepts/map/introduction.md b/concepts/map/introduction.md index aec5b1ec5..23d0473d0 100644 --- a/concepts/map/introduction.md +++ b/concepts/map/introduction.md @@ -1,14 +1,15 @@ # Introduction -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`: +In its most basic form, the higher-order function `map` accepts two arguments: a function `f` and a collection `coll`. +It applies `f` to each element of `coll`, returning a list of the results in the same order. ```clojure (map inc [1 2 3]) ; => (2 3 4) ``` -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]. +The previous example applies the function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result as the 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": +Let's see another example where we greet someone with the message "Welcome": ```clojure (defn say-welcome diff --git a/concepts/map/links.json b/concepts/map/links.json index 9395ad835..7bcefa9bf 100644 --- a/concepts/map/links.json +++ b/concepts/map/links.json @@ -1,10 +1,6 @@ [ { - "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" + "url": "https://clojuredocs.org/clojure.core/map", + "description": "ClojureDocs: map" } -] \ No newline at end of file +] diff --git a/concepts/reduce/.meta/config.json b/concepts/reduce/.meta/config.json index 4dd0dbc9f..a4f65382b 100644 --- a/concepts/reduce/.meta/config.json +++ b/concepts/reduce/.meta/config.json @@ -1,4 +1,4 @@ { - "blurb": "We provide here a basic explanation about the reduce higher-order function and its most common usage.", + "blurb": "The reduce function combines all elements of a collection into a single value by applying a function cumulatively", "authors": ["JaumeAmoresDS"] - } \ No newline at end of file + } diff --git a/concepts/reduce/links.json b/concepts/reduce/links.json index be49344d7..5dbca2e07 100644 --- a/concepts/reduce/links.json +++ b/concepts/reduce/links.json @@ -1,26 +1,6 @@ [ { - "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" + "url": "https://clojuredocs.org/clojure.core/reduce", + "description": "ClojureDocs: reduce" } -] \ No newline at end of file +] From 871b3e94ea42671af5bb695043c4d95f107b7af7 Mon Sep 17 00:00:00 2001 From: Jaume Amores <124528012+JaumeAmoresDS@users.noreply.github.com> Date: Tue, 1 Jul 2025 12:06:45 +0100 Subject: [PATCH 08/11] Update about.md --- concepts/map/about.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/concepts/map/about.md b/concepts/map/about.md index 2b8bad217..4479bdab1 100644 --- a/concepts/map/about.md +++ b/concepts/map/about.md @@ -1,13 +1,14 @@ # Introduction In its most basic form, the higher-order function `map` accepts two arguments: a function `f` and a collection `coll`. -It applies `f` to each element of `coll`, returning a list of the results in the same order. + +Given `f` and `coll`, `map` applies `f` to each element of `coll`, returning a list of the results in the same order. ```clojure (map inc [1 2 3]) ; => (2 3 4) ``` -The previous example applies the function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result as the 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 the function `inc` to each element of the vector and returns the result as the list `(2 3 4)`. Let's see another example where we greet someone with the message "Welcome": @@ -22,15 +23,21 @@ Let's see another example where we greet someone with the message "Welcome": ## Returning a lazy sequence Previously we provided a simplified explanation of how `map` operates. + In reality, `map` does not return a list but a *lazy* sequence. -Although an explanation of lazy sequences is beyond the scope of this introduction, it essentially means that the elements of the resulting sequence are not generated until they are actually needed. + +This essentially means that the elements of the resulting sequence are not generated until they are actually needed. + In other words, the function `f` passed to `map` is not applied until the elements of the resulting sequence are requested, for example, by printing them or retrieving their values. + This is advantageous when `f` is computationally expensive and only some elements are actually needed. ## Multiple collections `map` allows multiple collections to be passed as input. + If the number of collections passed is `n`, the function `f` will receive `n` arguments, one from each collection. + The result is a sequence where the i-th element is obtained by applying `f` to the `n` elements at position i from each collection. ```clojure From 41eb19777733c2ea393fbbea35b6de826c1305b9 Mon Sep 17 00:00:00 2001 From: Jaume Amores <124528012+JaumeAmoresDS@users.noreply.github.com> Date: Tue, 1 Jul 2025 12:07:12 +0100 Subject: [PATCH 09/11] Update introduction.md --- concepts/map/introduction.md | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/concepts/map/introduction.md b/concepts/map/introduction.md index 23d0473d0..4479bdab1 100644 --- a/concepts/map/introduction.md +++ b/concepts/map/introduction.md @@ -1,13 +1,14 @@ # Introduction In its most basic form, the higher-order function `map` accepts two arguments: a function `f` and a collection `coll`. -It applies `f` to each element of `coll`, returning a list of the results in the same order. + +Given `f` and `coll`, `map` applies `f` to each element of `coll`, returning a list of the results in the same order. ```clojure (map inc [1 2 3]) ; => (2 3 4) ``` -The previous example applies the function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result as the 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 the function `inc` to each element of the vector and returns the result as the list `(2 3 4)`. Let's see another example where we greet someone with the message "Welcome": @@ -17,4 +18,32 @@ Let's see another example where we greet someone with the message "Welcome": (str "Welcome " name "!")) (map say-welcome ["Chris" "Jane" "Peter"]) ; => ("Welcome Chris!" "Welcome Jane!" "Welcome Peter!") -``` \ No newline at end of file +``` + +## Returning a lazy sequence + +Previously we provided a simplified explanation of how `map` operates. + +In reality, `map` does not return a list but a *lazy* sequence. + +This essentially means that the elements of the resulting sequence are not generated until they are actually needed. + +In other words, the function `f` passed to `map` is not applied until the elements of the resulting sequence are requested, for example, by printing them or retrieving their values. + +This is advantageous when `f` is computationally expensive and only some elements are actually needed. + +## Multiple collections + +`map` allows multiple collections to be passed as input. + +If the number of collections passed is `n`, the function `f` will receive `n` arguments, one from each collection. + +The result is a sequence where the i-th element is obtained by applying `f` to the `n` elements at position i from 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 b159677f3bfc2012cf6546ca9dfc43396d730d46 Mon Sep 17 00:00:00 2001 From: Jaume Amores <124528012+JaumeAmoresDS@users.noreply.github.com> Date: Tue, 1 Jul 2025 12:16:18 +0100 Subject: [PATCH 10/11] Update about.md --- concepts/reduce/about.md | 58 ++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/concepts/reduce/about.md b/concepts/reduce/about.md index eafbf24bf..414d20426 100644 --- a/concepts/reduce/about.md +++ b/concepts/reduce/about.md @@ -1,6 +1,20 @@ # Introduction -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: +The higher-order funcion `reduce` accepts either two or three arguments. + +Let's see each one in turn. + +## Reduce with three arguments + +When called with three arguments, `(reduce f val coll)` runs sequentially as follows: + +- First, it 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]) @@ -11,7 +25,15 @@ The higher-order funcion `reduce` accepts either two or three arguments. When ca ;=> 110 ``` -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: +## Reduce with two arguments + +When called with two arguments, `(reduce f coll)` runs sequentially as follows: + +- First, it applies the function `f` to the two first elements `x_1` and `x_2` of the collection `coll`. +- Then, it 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]) @@ -21,7 +43,9 @@ When called with only two arguments, `(reduce f coll)` applies the function `f` ;=> 10 ``` -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: +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 @@ -31,7 +55,14 @@ In all these cases, the function `f` must accept either two arguments or a varia coll)) ``` -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: +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]) @@ -42,16 +73,27 @@ 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. +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. +- 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. +`reduce` accepts any type of collection, including one that contains functions. + +In that case, we will typically use three arguments `(reduce f val coll)`: + +- The function `f` applies the first function `g_1` from the collection to `val` +- Then applies 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 f240a08b47102dc5ea93e5091cfe3cfa10524de6 Mon Sep 17 00:00:00 2001 From: Jaume Amores <124528012+JaumeAmoresDS@users.noreply.github.com> Date: Tue, 1 Jul 2025 12:16:38 +0100 Subject: [PATCH 11/11] Update introduction.md --- concepts/reduce/introduction.md | 64 ++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/concepts/reduce/introduction.md b/concepts/reduce/introduction.md index 82318ea5b..414d20426 100644 --- a/concepts/reduce/introduction.md +++ b/concepts/reduce/introduction.md @@ -1,6 +1,20 @@ # Introduction -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: +The higher-order funcion `reduce` accepts either two or three arguments. + +Let's see each one in turn. + +## Reduce with three arguments + +When called with three arguments, `(reduce f val coll)` runs sequentially as follows: + +- First, it 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]) @@ -11,7 +25,15 @@ The higher-order funcion `reduce` accepts either two or three arguments. When ca ;=> 110 ``` -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: +## Reduce with two arguments + +When called with two arguments, `(reduce f coll)` runs sequentially as follows: + +- First, it applies the function `f` to the two first elements `x_1` and `x_2` of the collection `coll`. +- Then, it 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]) @@ -21,7 +43,9 @@ When called with only two arguments, `(reduce f coll)` applies the function `f` ;=> 10 ``` -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: +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 @@ -31,7 +55,14 @@ In all these cases, the function `f` must accept either two arguments or a varia coll)) ``` -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: +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]) @@ -42,4 +73,27 @@ 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. \ No newline at end of file +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)`: + +- The function `f` applies the first function `g_1` from the collection to `val` +- Then applies 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. +