Skip to content

Commit d82a638

Browse files
committed
Further improvements.
1 parent ebdcacc commit d82a638

File tree

2 files changed

+65
-48
lines changed

2 files changed

+65
-48
lines changed

11_functional_programming.ipynb

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -68,25 +68,28 @@
6868
"\n",
6969
"- The [functools reference](https://docs.python.org/3/library/functools.html) from the Python standard library (text)\n",
7070
"- The [itertools reference](https://docs.python.org/3/library/itertools.html) from the Python standard library (text)\n",
71-
"- [Functional programming howto](https://docs.python.org/3/howto/functional.html) from the Python documentation\n",
71+
"- [Functional programming howto](https://docs.python.org/3/howto/functional.html) from the Python documentation (text)\n",
7272
"- A [good, but old introduction](https://www.youtube.com/watch?v=Ta1bAMOMFOI) of functional programming (video)\n",
7373
"- A [very high level](https://www.youtube.com/watch?v=Qa8IfEeBJqk) introduction of functional programming (video, advanced). Interesting, but not Python-specific as it refers to Haskell\n",
7474
"- A [general introduction on functional programming](https://www.youtube.com/watch?v=8z_bUIl_uPo). Very worth watching as it uses Python for the examples (video)\n",
75-
"- [Principles of functional programming](https://dev.to/jamesrweb/principles-of-functional-programming-4b7c)\n",
75+
"- [Principles of functional programming](https://dev.to/jamesrweb/principles-of-functional-programming-4b7c) (text)\n",
7676
"\n",
7777
"# Introduction\n",
7878
"Functional programming is an approach to programming where programs are built by composing and running functions that perform a series of transformations on data.\n",
7979
"This contrasts with the approach of *imperative programming*, where programs are written as a series of statements which modify the *state* of the computation environment.\n",
8080
"Typically, within functional programming, great focus is placed on *composition*, *immutability* and *purity*.\n",
8181
"We are going to define these terms in more detail later.\n",
8282
"\n",
83-
"# Why Functional Programming\n",
84-
"Why do we choose functional programming? There are a series of advantages to this approach, namely:\n",
83+
"# Why Functional Programming?\n",
84+
"There are a series of advantages to this approach, namely:\n",
8585
"- **Debugging** and **testing** are easy: there are no surprises because every function only does one thing and does not affect any other piece of the program.\n",
86-
"- **Parallelization** is trivial: functions are just small *boxes* that take one input and produce an output and do not depend on other parts of the code in an implicit manner through global variables or other shared pieces of state. Thus, it's easy to make several functions run in parallel.\n",
86+
"- **Parallelization** is trivial: functions are just small *boxes* that take one input and produce an output and do not depend on other parts of the code in an implicit manner through global variables or other shared pieces of state.\n",
87+
" Thus, it's easy to make several functions run in parallel.\n",
8788
"\n",
8889
"# The basic principles of functional programming\n",
89-
"All modern programming languages have *functions* (or methods, procedures, subroutines, subprograms); these are groups of program statements that perform a certain computation. Functions are defined once for the whole program and can be reused at will throughout the program whenever we need to perform the specific computation they are defined for. Using functions, we can split our code in smaller units that are only responsible for a specific *functionality*; this helps us structuring our code in a clean and understandable form. \n",
90+
"All modern programming languages have *functions* (or methods, procedures, subroutines, subprograms); these are groups of program statements that perform a certain computation.\n",
91+
"Functions are defined once for the whole program and can be reused at will throughout the program whenever we need to perform the specific computation they are defined for.\n",
92+
"Using functions, we can split our code in smaller units that are only responsible for a specific *functionality*; this helps us structuring our code in a clean and understandable form. \n",
9093
"\n",
9194
"The main principles we use in functional programming are:\n",
9295
"\n",
@@ -119,9 +122,11 @@
119122
"In functional programming, we try to strive for *purity*. We want to define and use functions that only depend on their input, always return the same output for the same inputs and do not have any *side effects*, that is they do not indirectly affect any other part of our program.\n",
120123
"You can think of these functions as mathematical functions. \n",
121124
"Some examples of side effects are:\n",
122-
"- printing to the program output\n",
123-
"- reading or writing files\n",
124-
"- generating and using random numbers\n",
125+
"\n",
126+
"- Printing to the program output\n",
127+
"- Reading or writing files\n",
128+
"- Generating and using random numbers\n",
129+
"\n",
125130
"To better understand this concept, let us look at the function `my_first_pure_function` we defined below:"
126131
]
127132
},
@@ -228,14 +233,16 @@
228233
"cell_type": "markdown",
229234
"metadata": {},
230235
"source": [
231-
"As you see from the output, the function modified the list `x`. Therefore, this function is **not pure**. This leads us to the next principle, **immutability**."
236+
"As you see from the output, the function modified the list `x`.\n",
237+
"Therefore, this function is **not pure**.\n",
238+
"This leads us to the next principle, **immutability**."
232239
]
233240
},
234241
{
235242
"cell_type": "markdown",
236243
"metadata": {},
237244
"source": [
238-
"### Example\n",
245+
"### Exercise on pure functions\n",
239246
"\n",
240247
"<div class=\"alert alert-block alert-warning\">\n",
241248
" <h4><b>Question</b></h4>\n",
@@ -270,8 +277,8 @@
270277
" Returns:\n",
271278
" - the appended list\n",
272279
" \"\"\"\n",
273-
"\n",
274-
" return"
280+
" array.append(new_element)\n",
281+
" return array"
275282
]
276283
},
277284
{
@@ -370,7 +377,8 @@
370377
"tags": []
371378
},
372379
"source": [
373-
"Another important aspect of functional programming is `composition`. This means that whenever we want to perform multiple operations, we avoid writing intermediate variables, especially so when these are in the global state of the program.\n",
380+
"Another important aspect of functional programming is `composition`.\n",
381+
"This means that whenever we want to perform multiple operations, we avoid writing intermediate variables, especially so when these are in the global state of the program.\n",
374382
"\n",
375383
"Instead, we design our functions in such a way that one function's output can be directly fed into the next function.\n",
376384
"\n",
@@ -418,7 +426,7 @@
418426
"cell_type": "markdown",
419427
"metadata": {},
420428
"source": [
421-
"### Example\n",
429+
"### Exercise on composition\n",
422430
"\n",
423431
"<div class=\"alert alert-block alert-warning\">\n",
424432
" <h4><b>Question</b></h4>\n",
@@ -475,8 +483,7 @@
475483
},
476484
"source": [
477485
"Another important principle of functional programming is that **functions are values**.\n",
478-
"In programming languages (like Python) that support a functional style, we can manipulate functions with the language, we can pass them around in a variable and even use functions as parameters for another function.\n",
479-
"\n",
486+
"In programming languages that support a functional style (like Python), functions are treated as first-class objects: we can assign them to variables, pass them as arguments to other functions, and manipulate them just like any other value.\n",
480487
"As an example, consider the function `function_caller`:"
481488
]
482489
},
@@ -507,7 +514,8 @@
507514
}
508515
},
509516
"source": [
510-
"This function takes a function `f` and its argument `args` as input and returns the result of calling `f`, while additionally printing a message on the standard output of the program. Let's try this out.\n",
517+
"This function takes a function `f` and its argument `args` as input and returns the result of calling `f`, while additionally printing a message on the standard output of the program.\n",
518+
"Let's try this out.\n",
511519
"To do so, we define a new function `add_five`:"
512520
]
513521
},
@@ -558,10 +566,9 @@
558566
},
559567
"source": [
560568
"This example was a bit convoluted, but it shows that in Python we can use functions as values and even pass them as arguments to other functions.\n",
561-
"\n",
562569
"This is useful in many cases; a typical example is numerical optimization, where we want to find the parameters of a function that minimize a certain criterion.\n",
563-
"\n",
564-
"Other than these specific use cases, there are some common *higher-order functions*, or functions that take other functions as parameters, that are common in most programming languages.We will look at a few of them in a following section."
570+
"Other than these specific use cases, there are some common *higher-order functions*, or functions that take other functions as parameters, that are common in most programming languages.\n",
571+
"We will look at a few of them in a following section."
565572
]
566573
},
567574
{
@@ -582,7 +589,8 @@
582589
"tags": []
583590
},
584591
"source": [
585-
"Another principle of functional programming is called [**referential transparency**](https://en.wikipedia.org/wiki/Referential_transparency). This complex-sounding name simply means that we can replace any expression with its value without changing the behavior of the program.\n",
592+
"Another principle of functional programming is called [**referential transparency**](https://en.wikipedia.org/wiki/Referential_transparency).\n",
593+
"This complex-sounding name simply means that we can replace any expression with its value without changing the behavior of the program.\n",
586594
"\n",
587595
"Consider the following Python code:"
588596
]
@@ -613,7 +621,8 @@
613621
}
614622
},
615623
"source": [
616-
"If `transparent_function` is referentially transparent, we can replace its value in the expression `y1 = transparent_function(1) + 5` and we obtain the same value. In other words, the value of `y` does not change if we execute it again, as shown in the code above: we have two expressions `y1` and `y2` that use the same code and because `x` is referentially transparent, their values are equal.\n",
624+
"If `transparent_function` is referentially transparent, we can replace its value in the expression `y1 = transparent_function(1) + 5` and we obtain the same value.\n",
625+
"In other words, the value of `y` does not change if we execute it again, as shown in the code above: we have two expressions `y1` and `y2` that use the same code and because `x` is referentially transparent, their values are equal.\n",
617626
"\n",
618627
"In this case, this is true because the value of `x` is simply 1 and is invariant.\n",
619628
"\n",
@@ -730,7 +739,8 @@
730739
"metadata": {},
731740
"source": [
732741
"In a language with stricter typing discipline like Java or C++, we would not even be able to compile our program.\n",
733-
"Python lacks this strictness. However, there are tools like [mypy](https://mypy-lang.org/) that can be used to check the type consistency of Python programs without running them.\n",
742+
"Python lacks this strictness.\n",
743+
"However, there are tools like [mypy](https://mypy-lang.org/) that can be used to check the type consistency of Python programs without running them.\n",
734744
"\n",
735745
"We can achieve a similar effect using execution-time type checks.\n",
736746
"To facilitate our work, we first construct a `Person` class that encapsulates the attributes of a person.\n",
@@ -771,7 +781,8 @@
771781
"cell_type": "markdown",
772782
"metadata": {},
773783
"source": [
774-
"Because of the `isinstance` check in `__init_`, we aren't able to construct a `Person` if `age` is a string (type `str`). Likewise, we cannot call `greet_better` with a value other than a `Person`.\n"
784+
"Because of the `isinstance` check in `__init_`, we aren't able to construct a `Person` if `age` is a string (type `str`).\n",
785+
"Likewise, we cannot call `greet_better` with a value other than a `Person`.\n"
775786
]
776787
},
777788
{
@@ -928,9 +939,10 @@
928939
},
929940
"source": [
930941
"## Filtering\n",
931-
"Another basic higher order function is `filter`.\n",
942+
"Another basic higher order function (HoF) is `filter`.\n",
932943
"As the name says, this function is used to filter an `iterable` using a *predicate function*.\n",
933-
"This is a function that takes a value and returns `true` or `false`. With `true`, the current element is kept, with `false` it is discarded.\n",
944+
"This is a function that takes a value and returns `true` or `false`.\n",
945+
"With `true`, the current element is kept, with `false` it is discarded.\n",
934946
"We now try to write a predicate function that only keeps even numbers.\n",
935947
"\n",
936948
"<a name=\"filter-example\"></a>"
@@ -958,7 +970,8 @@
958970
}
959971
},
960972
"source": [
961-
"Now we are ready to try `filter` using `is_even`. Because `filter` returns a `filter` object, we wrap it in `list` to directly see the result as a list:"
973+
"Now we are ready to try `filter` using `is_even`.\n",
974+
"Because `filter` returns a `filter` object, we wrap it in `list` to directly see the result as a list:"
962975
]
963976
},
964977
{
@@ -984,14 +997,15 @@
984997
},
985998
"source": [
986999
"## Reducing\n",
987-
"A third basic HoF is *reduction*.\n",
1000+
"The third basic HoF is *reduction*.\n",
9881001
"This is a function that takes a function `f(x, y)` of two arguments and an iterable `it` and applies the function to every element in the iterable *cumulatively* to produce one value.\n",
9891002
"It works in the following way:\n",
9901003
"\n",
9911004
"- The first argument of `f`, `x`, is the **current value of the accumulation**. In the beginning, this corresponds to the first element of `it`.\n",
9921005
"- The second argument of `f`, `y`, is the **current element of the iterable**. In the beginning, this corresponds to the second element of `it`.\n",
9931006
"\n",
994-
"Because of this behaviour, this function is handy to compute sums or similar aggregations over a list. In Python, this function is available in the [`functools` module](https://docs.python.org/3/library/functools.html), part of Python's standard library.\n",
1007+
"Because of this behaviour, this function is handy to compute sums or similar aggregations over a list.\n",
1008+
"In Python, this function is available in the [`functools` module](https://docs.python.org/3/library/functools.html), part of Python's standard library.\n",
9951009
"\n",
9961010
"As an example of using `reduce`, consider the following snippet:\n"
9971011
]
@@ -1033,7 +1047,7 @@
10331047
"cell_type": "markdown",
10341048
"metadata": {},
10351049
"source": [
1036-
"### Examples: Iteration and Mapping"
1050+
"### Exercises on Iteration and Mapping"
10371051
]
10381052
},
10391053
{
@@ -1282,7 +1296,7 @@
12821296
"cell_type": "markdown",
12831297
"metadata": {},
12841298
"source": [
1285-
"### Example: Keeping only multiples of n\n",
1299+
"### Exercise: Keeping only multiples of `n`\n",
12861300
"\n",
12871301
"<div class=\"alert alert-block alert-warning\">\n",
12881302
" <h4><b>Question</b></h4>\n",
@@ -1321,7 +1335,7 @@
13211335
"source": [
13221336
"%%ipytest\n",
13231337
"\n",
1324-
"def solution_multiples_of_n(l: \"list[int]\", k: int):\n",
1338+
"def solution_multiples_of_n(my_list: \"list[int]\", k: int) -> \"list[int]\":\n",
13251339
" \"\"\"A function that keeps only the multiples of k from a given list.\n",
13261340
"\n",
13271341
" Args:\n",
@@ -1331,7 +1345,7 @@
13311345
" - the filtered list\n",
13321346
" \"\"\"\n",
13331347
"\n",
1334-
" return"
1348+
" return [a for a in my_list if a % k == 0]"
13351349
]
13361350
},
13371351
{
@@ -1348,9 +1362,11 @@
13481362
"\n",
13491363
"Lets see some example of problems we can solve by using these tools.\n",
13501364
"\n",
1351-
"- Iterating over two lists in parallel: \n",
1352-
" consider the two lists `numbers = [1, 2, 3, 4, 5]` and `words = [\"one\", \"two\", \"three\" , \"four\", \"five\"]`. We want to produce a list of strings consisting of the content of `numbers` and `words`, such that each number in `numbers` corresponds to the right word in `words`. We can do this by using the `zip` buil-in function:\n",
1353-
"`zip` takes a number of `iterables` as an input and returns a list of tuples. The i-th element of this list is a tuple with the i-th element of each iterable, proceeding until the shortes iterable is exhausted. Using `zip`, we solve our problem like this:"
1365+
"- Iterating over two lists in parallel: consider the two lists `numbers = [1, 2, 3, 4, 5]` and `words = [\"one\", \"two\", \"three\" , \"four\", \"five\"]`.\n",
1366+
" We want to produce a list of strings consisting of the content of `numbers` and `words`, such that each number in `numbers` corresponds to the right word in `words`.\n",
1367+
" We can do this by using the `zip` buil-in function: `zip` takes a number of `iterables` as an input and returns a list of tuples.\n",
1368+
" The `i-th` element of this list is a tuple with the `i-th` element of each iterable, proceeding until the shortes iterable is exhausted.\n",
1369+
" Using `zip`, we solve our problem like this:"
13541370
]
13551371
},
13561372
{
@@ -1376,10 +1392,10 @@
13761392
}
13771393
},
13781394
"source": [
1379-
"- Producing permutations of elements.\n",
1380-
" Suppose we have a list containing the letters `[\"A\", \"B\", \"C\", \"D\", \"E\"]` and we want to produce all four letter words from them.\n",
1381-
" We can use `itertools.permutations`.\n",
1382-
" Once again, we wrap the operation in `list` to obtain a list as output:"
1395+
"- Producing permutations of elements.\n",
1396+
" Suppose we have a list containing the letters `[\"A\", \"B\", \"C\", \"D\", \"E\"]` and we want to produce all four letter words from them.\n",
1397+
" We can use `itertools.permutations`.\n",
1398+
" Once again, we wrap the operation in `list` to obtain a list as output:"
13831399
]
13841400
},
13851401
{
@@ -1405,7 +1421,9 @@
14051421
}
14061422
},
14071423
"source": [
1408-
"Another useful trick to know when working with lists is [**unpacking**](https://docs.python.org/3/tutorial/controlflow.html#tut-unpacking-arguments). In Python, we can extract elements from a list using the assignment statement. For example, if we have a two-element list, we can write"
1424+
"- [**Unpacking**](https://docs.python.org/3/tutorial/controlflow.html#tut-unpacking-arguments) is another useful trick to know when working with lists. \n",
1425+
" In Python, we can extract elements from a list using the assignment statement.\n",
1426+
" For example, if we have a two-element list, we can write"
14091427
]
14101428
},
14111429
{
@@ -1795,7 +1813,7 @@
17951813
"name": "python",
17961814
"nbconvert_exporter": "python",
17971815
"pygments_lexer": "ipython3",
1798-
"version": "3.11.3"
1816+
"version": "3.12.10"
17991817
},
18001818
"vscode": {
18011819
"interpreter": {

tutorial/tests/test_11_functional_programming.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616

1717

1818
def reference_pure_function(array, new_element):
19-
array.append(new_element)
20-
return array
19+
return array + [new_element]
2120

2221

2322
@pytest.mark.parametrize(
@@ -28,9 +27,9 @@ def reference_pure_function(array, new_element):
2827
],
2928
)
3029
def test_pure_function(array, new_element, function_to_test):
31-
assert function_to_test(array, new_element) == reference_pure_function(
32-
array, new_element
33-
)
30+
output_array = function_to_test(array, new_element)
31+
assert id(output_array) != id(array), "The arrays must be different objects."
32+
assert output_array == reference_pure_function(array, new_element)
3433

3534

3635
#

0 commit comments

Comments
 (0)