Skip to content

Commit

Permalink
lint software arch
Browse files Browse the repository at this point in the history
  • Loading branch information
alasdairwilson committed Jul 15, 2024
1 parent b1e8617 commit d648c0a
Show file tree
Hide file tree
Showing 22 changed files with 805 additions and 855 deletions.
2 changes: 1 addition & 1 deletion introductory_courses/python/08_lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ x = [['pepper', 'zucchini', 'onion'],

Here is a visual example of how indexing a list of lists `x` works:

[![x is represented as a pepper shaker containing several packets of pepper. [x[0]] is represented as a pepper shaker containing a single packet of pepper. x[0] is represented as a single packet of pepper. x\[0]\[0] is represented as single grain of pepper. Adapted from @hadleywickham.](fig/indexing_lists_python.png)](https://twitter.com/hadleywickham/status/643381054758363136)
[![x is represented as a pepper shaker containing several packets of pepper. [x[0]] is represented as a pepper shaker containing a single packet of pepper. x[0] is represented as a single packet of pepper. x\[0]\[0] is represented as single grain of pepper. Adapted from @hadleywickham.](fig/indexing_lists_python.png)](<https://twitter.com/hadleywickham/status/643381054758363136>)

Using the previously declared list `x`, these would be the results of the
index operations shown in the image:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
---
name: Higher Order Functions
dependsOn: [
software_architecture_and_design.functional.side_effects_cpp,
]
dependsOn: [software_architecture_and_design.functional.side_effects_cpp]
tags: [cpp]
attribution:
- citation: >
This material was adapted from an "Introduction to C++" course developed by the
Oxford RSE group.
url: https://www.rse.ox.ac.uk
image: https://www.rse.ox.ac.uk/images/banner_ox_rse.svg
license: CC-BY-4.0
- citation: This course material was developed as part of UNIVERSE-HPC, which is funded through the SPF ExCALIBUR programme under grant number EP/W035731/1
url: https://www.universe-hpc.ac.uk
image: https://www.universe-hpc.ac.uk/assets/images/universe-hpc.png
license: CC-BY-4.0

attribution:
- citation: >
This material was adapted from an "Introduction to C++" course developed by the
Oxford RSE group.
url: https://www.rse.ox.ac.uk
image: https://www.rse.ox.ac.uk/images/banner_ox_rse.svg
license: CC-BY-4.0
- citation: This course material was developed as part of UNIVERSE-HPC, which is funded through the SPF ExCALIBUR programme under grant number EP/W035731/1
url: https://www.universe-hpc.ac.uk
image: https://www.universe-hpc.ac.uk/assets/images/universe-hpc.png
license: CC-BY-4.0
---


## First Class Functions

Languages that treat functions as first-class citizens allow functions to be
Expand All @@ -27,7 +23,7 @@ variables. In C++ this is typically done via lamda functions or function objects

### Lambda Functions

*Lambda functions* are small, nameless functions which are defined in the
_Lambda functions_ are small, nameless functions which are defined in the
normal flow of the program, typically as they are needed. They consist of three part,
delimited by square, round, then curly brackets. The curly brackets form the
body of the function, for example
Expand Down Expand Up @@ -147,7 +143,7 @@ for (const auto& op: ops) {
std::cout << result << std::end; // prints 6
```

`std::function` is an example of *type erasure*.
`std::function` is an example of _type erasure_.

## Higher Order Functions

Expand Down Expand Up @@ -191,17 +187,17 @@ int reduce(const std::vector<int>& data, std::function<int(int, int)> bin_op) {
int main() {
std::vector<int> data = {1, 2, 3, 4, -1};
std::cout << reduce(data, std::plus<int>()) << std::endl;
std::cout << reduce(data, std::multiplies<int>()) << std::endl;
std::cout << reduce(data, [](int a, int b) { return std::max(a, b); }) << std::endl;
std::cout << reduce(data, [](int a, int b) { return std::min(a, b); }) << std::endl;
std::cout << reduce(data, std::plus<int>()) << std::endl;
std::cout << reduce(data, std::multiplies<int>()) << std::endl;
std::cout << reduce(data, [](int a, int b) { return std::max(a, b); }) << std::endl;
std::cout << reduce(data, [](int a, int b) { return std::min(a, b); }) << std::endl;
}
```

Excellent! We have reduced the amount of code we need to write, reducing the
number of possible bugs and making the code easier to maintain in the future.

C++ actually has a `std::reduce`, which is part of the *algorithms* standard library.
C++ actually has a `std::reduce`, which is part of the _algorithms_ standard library.

### The Algorithms Library

Expand All @@ -213,7 +209,7 @@ recognising their conceptual similarities. Using the algorithms library means:
(a) you reduce the amount of (algorithmic) code you need to write, reducing bugs and increasing maintainability
(b) you make clear to the reader what your code is doing, since these are commonly used algorithms
(b) you benifit from bullet proof, efficient implementations written by the same teams that write the compiler you are using
(c) you can benifit from *executors* to instantly parallise or vectorise your code for high performance.
(c) you can benifit from _executors_ to instantly parallise or vectorise your code for high performance.

Lets go through a few examples inspired by the common functional algorithms
"map", "filter" and "reduce" (also the inspiration for the MapReduce
Expand All @@ -225,13 +221,13 @@ First the map, or `std::transform`:
std::vector<double> data = {1.0, 2.0, -1.1, 5.0};

// transform in-place
std::transform(std::begin(data), std::end(data), std::begin(data),
std::transform(std::begin(data), std::end(data), std::begin(data),
[](const double& x) { return 2.0 * x; } );

std::vector<double> new_data(data.size());

// transform to a new collection
std::transform(std::begin(data), std::end(data), std::begin(new_data),
std::transform(std::begin(data), std::end(data), std::begin(new_data),
[](const double& x) { return 3.14 * std::pow(x, 2); } );

```
Expand Down Expand Up @@ -259,7 +255,7 @@ bool is_prime(int n) {
}
int main() {
std::vector<int> data(1000);
std::vector<int> data(1000);
std::iota(data.begin(), data.end(), 1); // fill with numbers 1 -> 1000
std::copy_if(data.begin(), data.end(),
std::ostream_iterator<int>(std::cout, " "),
Expand All @@ -278,7 +274,7 @@ maximum elements of an vector. At the same time we introduce another algorithm
`std::generate`, which assigns values to a range based on a generator function, and some
of the random number generation options in the standard library.

``` cpp
```cpp
#include <algorithm>
#include <iostream>
#include <functional>
Expand All @@ -295,7 +291,7 @@ int main() {
std::normal_distribution<double> dist(5, 2);
auto gen_random = [&]() { return dist(gen);};

std::vector<double> data(1000);
std::vector<double> data(1000);
std::generate(data.begin(), data.end(), gen_random);

auto calc_min_max = [](std::tuple<double, double> acc, double x) {
Expand All @@ -314,7 +310,7 @@ int main() {
Use `std::accumulate` to write a function that calculates the sum of the squares of the values in a vector.
Your function should behave as below:

``` cpp
```cpp
std::cout << sum_of_squares({0}) << std::endl;
std::cout << sum_of_squares({1, 3, -2}) << std::endl;
```
Expand Down Expand Up @@ -343,7 +339,7 @@ int sum_of_squares(const std::vector<int>& data) {
Now let's assume we're reading in these numbers from an input file, so they arrive as a list of strings.
Write a new function `map_str_to_int` using `std::transform` that passes the following tests:
``` cpp
```cpp
std::cout << sum_of_squares(map_str_to_int({"1", "2", "3"})) << std::endl;
std::cout << sum_of_squares(map_str_to_int({"-1", "-2", "-3"})) << std::endl;
```
Expand All @@ -369,7 +365,7 @@ const std::vector<int> map_str_to_int(const std::vector<std::string>& data) {
Finally, we'd like it to be possible for users to comment out numbers in the input file they give to our program.
Extend your `map_str_to_int` function so that the following tests pass:
``` cpp
```cpp
std::cout << sum_of_squares(map_str_to_int({"1", "2", "3"})) << std::endl;
std::cout << sum_of_squares(map_str_to_int({"1", "2", "#100", "3"})) << std::endl;
```
Expand Down Expand Up @@ -408,13 +404,13 @@ std::vector<int> map_str_to_int(const std::vector<std::string>& data) {
}
new_data.push_back(std::atoi(x.c_str()));
}
return new_data;
return new_data;
}
```

Here you can start to see a limitation of the algorithms in the standard
library, in that it is difficult to efficiently compose together multiple
elemental algorithms into more complex algorithm. The *ranges* library is an
elemental algorithms into more complex algorithm. The _ranges_ library is an
C++20 addition to the standard library aims to solve this problem, you can read
more about the ranges library [here](https://en.cppreference.com/w/cpp/ranges).

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
---
name: Higher Order Functions
dependsOn: [
software_architecture_and_design.functional.side_effects_python,
]
dependsOn: [software_architecture_and_design.functional.side_effects_python]
tags: [python]
attribution:
- citation: This material has been adapted from the "Software Engineering" module of the SABS R³ Center for Doctoral Training.
url: https://www.sabsr3.ox.ac.uk
image: https://www.sabsr3.ox.ac.uk/sites/default/files/styles/site_logo/public/styles/site_logo/public/sabsr3/site-logo/sabs_r3_cdt_logo_v3_111x109.png
license: CC-BY-4.0
- citation: This course material was developed as part of UNIVERSE-HPC, which is funded through the SPF ExCALIBUR programme under grant number EP/W035731/1
url: https://www.universe-hpc.ac.uk
image: https://www.universe-hpc.ac.uk/assets/images/universe-hpc.png
license: CC-BY-4.0

attribution:
- citation: This material has been adapted from the "Software Engineering" module of the SABS R³ Center for Doctoral Training.
url: https://www.sabsr3.ox.ac.uk
image: https://www.sabsr3.ox.ac.uk/sites/default/files/styles/site_logo/public/styles/site_logo/public/sabsr3/site-logo/sabs_r3_cdt_logo_v3_111x109.png
license: CC-BY-4.0
- citation: This course material was developed as part of UNIVERSE-HPC, which is funded through the SPF ExCALIBUR programme under grant number EP/W035731/1
url: https://www.universe-hpc.ac.uk
image: https://www.universe-hpc.ac.uk/assets/images/universe-hpc.png
license: CC-BY-4.0
---

## First Class Functions
Expand All @@ -24,7 +21,7 @@ variables. This is a powerful feature of functional programming languages, and i

In Python, functions are first-class citizens, which means that they can be passed to other functions as arguments, for example:

``` python
```python
def add_one(x):
return x + 1

Expand All @@ -40,15 +37,15 @@ print(apply_function(add_one, 1))

## Lambda Functions

*Lambda functions* are small, nameless functions which are defined in the
_Lambda functions_ are small, nameless functions which are defined in the
normal flow of the program, typically as they are needed. The structure of these
functions is not dissimilar to a normal python function definition - we have a
keyword `lambda`, a list of parameters, a colon, then the function body. In
keyword `lambda`, a list of parameters, a colon, then the function body. In
Python, the function body is limited to a single expression, which becomes the
return value.

``` python
add_one = lambda x: x + 1
```python
add_one = lambda x: x + 1 # NOQA E731

print(add_one(1))
```
Expand Down Expand Up @@ -127,14 +124,14 @@ number of possible bugs and making the code easier to maintain in the future.

Python has a number of higher order functions built in, including `map`,
`filter` and `reduce`. Note that the `map` and `filter` functions in Python use
**lazy evaluation**. This means that values in an iterable collection are not
actually calculated until you need them. We'll explain some of the implications
**lazy evaluation**. This means that values in an iterable collection are not
actually calculated until you need them. We'll explain some of the implications
of this a little later, but for now, we'll just use `list()` to convert the
results to a normal list. In these examples we also see the more typical usage
results to a normal list. In these examples we also see the more typical usage
of lambda functions.

The `map` function, takes a function and applies it to each value in an
**iterable**. Here, 'iterable' means any object that can be iterated over - for
**iterable**. Here, 'iterable' means any object that can be iterated over - for
more details see the [Iterable Abstract Base Class
documentation](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable).
The results of each of those applications become the values in the **iterable**
Expand All @@ -158,7 +155,7 @@ print(list(map(lambda x: x + 1, l)))

Like `map`, `filter` takes a function and applies it to each value in an iterable, keeping the value if the result of the function application is `True`.

``` python
```python
l = [1, 2, 3]

def is_gt_one(x):
Expand All @@ -178,7 +175,7 @@ The `reduce` function is different.
This function uses a function which accepts two values to accumulate the values in the iterable.
The simplest uses here are to calculate the sum or product of a sequence.

``` python
```python
from functools import reduce

l = [1, 2, 3]
Expand All @@ -202,9 +199,10 @@ These are the fundamental components of the MapReduce style, and can be combined
Using `map` and `reduce`, write a function that calculates the sum of the squares of the values in a list.
Your function should behave as below:

``` python
```python
def sum_of_squares(l):
# Your code here
return

print(sum_of_squares([0]))
print(sum_of_squares([1]))
Expand All @@ -223,7 +221,7 @@ print(sum_of_squares([-1, -2, -3]))

:::solution

``` python
```python
from functools import reduce

def sum_of_squares(l):
Expand All @@ -236,7 +234,7 @@ def sum_of_squares(l):
Now let's assume we're reading in these numbers from an input file, so they arrive as a list of strings.
Modify your function so that it passes the following tests:

``` python
```python
print(sum_of_squares(['1', '2', '3']))
print(sum_of_squares(['-1', '-2', '-3']))
```
Expand All @@ -248,7 +246,7 @@ print(sum_of_squares(['-1', '-2', '-3']))

:::solution

``` python
```python
from functools import reduce

def sum_of_squares(l):
Expand All @@ -262,7 +260,7 @@ def sum_of_squares(l):
Finally, like comments in Python, we'd like it to be possible for users to comment out numbers in the input file they give to our program.
Extend your function so that the following tests pass (don't worry about passing the first set of tests with lists of integers):

``` python
```python
print(sum_of_squares(['1', '2', '3']))
print(sum_of_squares(['-1', '-2', '-3']))
print(sum_of_squares(['1', '2', '#100', '3']))
Expand All @@ -276,7 +274,7 @@ print(sum_of_squares(['1', '2', '#100', '3']))

:::solution

``` python
```python
from functools import reduce

def sum_of_squares(l):
Expand Down Expand Up @@ -324,14 +322,14 @@ with, rather than always getting back a `map` or `filter` iterable.
### List Comprehensions

The **list comprehension** is probably the most commonly used comprehension
type. As you might expect from the name, list comprehensions produce a list
from some other iterable type. In effect they are the same as using `map`
type. As you might expect from the name, list comprehensions produce a list
from some other iterable type. In effect they are the same as using `map`
and/or `filter` and using `list()` to cast the result to a list, as we did
previously.

All comprehension types are structured in a similar way, using the syntax for a
literal of that type (in the case below, a list literal) containing what looks
like the top of a for loop. To the left of the `for` we put the equivalent of
like the top of a for loop. To the left of the `for` we put the equivalent of
the map operation we want to use:

```python
Expand Down Expand Up @@ -550,10 +548,10 @@ Took 0.124199753 seconds

## Key Points

- *First-Class Functions*: functions that can be passed as arguments to other functions, returned from functions, or assigned to variables.
- *Lambda Functions*: small, nameless functions defined in the normal flow of the program with a keyword lambda.
- *Higher-Order Functions*: a function that has other functions as one of its arguments.
- *Map, Filter and Reduce*: built-in higher order functions in Python that use lazy evaluation.
- *Comprehensions*: a more Pythonic way to structure map and filter operations.
- *Generators*: similar to list comprehensions, but behave differently and not evaluated until you iterate over them.
- *Decorators*: higher-order functions that take a function as an argument, modify it, and return it.
- _First-Class Functions_: functions that can be passed as arguments to other functions, returned from functions, or assigned to variables.
- _Lambda Functions_: small, nameless functions defined in the normal flow of the program with a keyword lambda.
- _Higher-Order Functions_: a function that has other functions as one of its arguments.
- _Map, Filter and Reduce_: built-in higher order functions in Python that use lazy evaluation.
- _Comprehensions_: a more Pythonic way to structure map and filter operations.
- _Generators_: similar to list comprehensions, but behave differently and not evaluated until you iterate over them.
- _Decorators_: higher-order functions that take a function as an argument, modify it, and return it.
Loading

0 comments on commit d648c0a

Please sign in to comment.