-
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b3b9347
commit d848b5e
Showing
26 changed files
with
876 additions
and
119 deletions.
There are no files selected for viewing
68 changes: 68 additions & 0 deletions
68
common-content/en/module/tools/comparing-javascript-and-python/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
+++ | ||
title = "Comparing JavaScript and Python" | ||
headless = true | ||
time = 40 | ||
facilitation = false | ||
emoji= "📖" | ||
objectives = [ | ||
"Identify and explain equivalences between JavaScript and Python", | ||
"Identify and explain differences between JavaScript and Python", | ||
"Distinguish between essential and accidental complexity" | ||
] | ||
+++ | ||
|
||
JavaScript and Python are quite similar languages in a lot of ways. | ||
|
||
Most of the differences between them are quite cosmetic. e.g. | ||
* Some functions and operators have different names. But often there are functions/operators which do exactly the same thing. | ||
* JavaScript uses `{}` around blocks of code and we optionally _choose_ to indent code, whereas Python uses `:` and _required_ indents. | ||
* In JavaScript we choose to name variables in `camelCase`, whereas in Python we choose to name variables in `snake_case` (but in both langues we _could_ do either). | ||
|
||
Let's take our "count containing words" JavaScript code from last week, and think about what it would look like in Python. | ||
|
||
```js | ||
import { program } from "commander"; | ||
import { promises as fs } from "node:fs"; | ||
import process from "node:process"; | ||
|
||
program | ||
.name("count-containing-words") | ||
.description("Counts words in a file that contain a particular character") | ||
.option("-c, --char <char>", "The character to search for", "-"); | ||
|
||
program.parse(); | ||
|
||
const argv = program.args; | ||
if (argv.length != 1) { | ||
console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); | ||
process.exit(1); | ||
} | ||
const path = argv[0]; | ||
const char = program.opts().char; | ||
|
||
const content = await fs.readFile(path, "utf-8"); | ||
const wordsContainingChar = content.split(" ").filter((word) => word.indexOf(char) > -1).length; | ||
console.log(wordsContainingChar); | ||
``` | ||
|
||
Let's think about what we're doing in this code. We're: | ||
* Parsing command line flags - writing down what flags we expect to be passed, and reading values for them based on the actual command line. | ||
* Validating the flags (i.e. checking that exactly one path was passed). | ||
* Reading a file. | ||
* Splitting the content of the file up into words. | ||
* Counting how many of the words contained a particular character. | ||
* Printing the count. | ||
|
||
These are the meaningful things we needed to do. If we wanted to solve the same problem with Python, we'd need to do all of these things. | ||
|
||
There are also some other things we did in our code, which were important, but not the point of the code. An example is, we imported some modules. We may need to import modules to write this code in Python. Or we may not. Importing modules wasn't one of our _goals_, it was just something we needed to do to help us. | ||
|
||
We split up things we need to do into two categories: essential and accidental. | ||
|
||
**Essential** means it is a core part of the problem. e.g. in order to count how many words are in a file, it is _essential_ that we read the file. | ||
|
||
**Accidental** means it isn't what we _care_ about doing, but we may need to do it anyway. e.g. importing the `process` module isn't _essential_ to our problem, but we needed to do it anyway so we could report errors. | ||
|
||
When we're thinking about how we use different languages, it's useful to think about what parts of our problem are _essential_ (we'll need to do them in any language), and which parts are _accidental_ (it's just something we had to do on the way to achieve our aim). | ||
|
||
Whether we write the JavaScript `someArray.length` or the Python `len(some_array)` isn't a big difference - both do the same thing, they just look a little a little different. |
12 changes: 0 additions & 12 deletions
12
common-content/en/module/tools/convert-script-between-languages/index.md
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
+++ | ||
title = "Putting it all together" | ||
headless = true | ||
time = 30 | ||
facilitation = false | ||
emoji= "📖" | ||
hide_from_overview = true | ||
objectives = [ | ||
] | ||
+++ | ||
|
||
Finally, instead of calling `console.log`, in Python we call `print`. | ||
|
||
```python | ||
import argparse | ||
|
||
parser = argparse.ArgumentParser( | ||
prog="count-containing-words", | ||
description="Counts words in a file that contain a particular character", | ||
) | ||
|
||
parser.add_argument("-c", "--char", help="The character to search for", default="-") | ||
parser.add_argument("path", help="The file to search") | ||
|
||
args = parser.parse_args() | ||
|
||
with open(args.path, "r") as f: | ||
content = f.read() | ||
words_containing_char = len(filter(lambda word: args.char in word, content.split(" "))) | ||
print(words_containing_char) | ||
``` | ||
|
||
This looks pretty similar to the JavaScript version. The essential shape is the same. But every line is a least a little bit different. | ||
|
||
Some programming languages are a lot more different. But JavaScript and Python are, essentially, quite similar. |
73 changes: 73 additions & 0 deletions
73
common-content/en/module/tools/converting-javascript-to-python/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
+++ | ||
title = "Converting JavaScript to Python" | ||
headless = true | ||
time = 40 | ||
facilitation = false | ||
emoji= "📖" | ||
objectives = [ | ||
"Rewrite JavaScript code as Python" | ||
] | ||
+++ | ||
|
||
### Parsing command line flags | ||
|
||
In JavaScript, we wrote this code (note: there was some other code in between some of these lines): | ||
|
||
```js | ||
import { program } from "commander"; | ||
|
||
program | ||
.name("count-containing-words") | ||
.description("Counts words in a file that contain a particular character") | ||
.option("-c, --char <char>", "The character to search for", "-"); | ||
|
||
program.parse(); | ||
|
||
const argv = program.args; | ||
const path = argv[0]; | ||
const char = program.opts().char; | ||
``` | ||
|
||
The _essential_ goals here are to: | ||
* Allow a user to pass a `-c` argument (defaulting to `-` if they don't). | ||
* Allow a user to pass a path as a positional argument. | ||
* Supply a nice `--help` implementation to help a user if they don't know how to use our tool. | ||
|
||
We _accidentally_ did a lot of things to achieve these goals. We used a library called commander. We imported that library. We called some particular functions, and made some particular variables. | ||
|
||
If we want to work out how to do this in Python, we should focus on the essential goals. We may want to search for things like "Parse command line flags Python" and "Default argument values Python" because they get to the essential problems we're trying to solve. | ||
|
||
Searching Google for "Parse command line flags Python" brought us to [the Python argparse documentation](https://docs.python.org/3/library/argparse.html). The example code looks pretty similar to what we were doing in Python. We can probably write something like: | ||
|
||
```python | ||
import argparse | ||
|
||
parser = argparse.ArgumentParser( | ||
prog="count-containing-words", | ||
description="Counts words in a file that contain a particular character", | ||
) | ||
|
||
parser.add_argument("-c", "--char", help="The character to search for", default="-") | ||
parser.add_argument("path", help="The file to search") | ||
|
||
args = parser.parse_args() | ||
``` | ||
|
||
There are some differences here. | ||
* With commander we were calling functions on a global `program`, whereas with argparse we construct a new `ArgumentParser` which we use. | ||
* `add_argument` takes separate parameters for the short (`-c`) and long (`--char`) forms of the option - `commander` expected them in one string. | ||
* The Python version uses a lot of named arguments (e.g. `add_argument(...)` took `help=`, `default=`), whereas the JavaScript version (`option(...)`) used a lot of positional ones. | ||
* The Python version handles positional arguments itself as arguments with names (`path`), whereas the JavaScript version just gives us an array of positional arguments and leaves us to understand them. | ||
|
||
### Validating command line flags | ||
|
||
In our JavaScript code, we needed to check that there was exactly one positional argument. | ||
|
||
We don't need to do this in our Python code. Because `argparse` treats positional arguments as arguments, it actually already errors if we pass no positional arguments, or more than one. | ||
|
||
So we can tick this essential requirement off of our list. Sometimes different languages, or different libraries, do things slightly differently, and that's ok! | ||
|
||
> [!TIP] | ||
> We don't need to convert every line. | ||
> | ||
> We're trying to convert _essential requirements_. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
+++ | ||
title = "Counting words containing a character" | ||
headless = true | ||
time = 15 | ||
facilitation = false | ||
emoji= "📖" | ||
hide_from_overview = true | ||
objectives = [ | ||
] | ||
+++ | ||
|
||
In JavaScript we wrote: | ||
|
||
```js | ||
content.split(" ").filter((word) => word.indexOf(char) > -1).length | ||
``` | ||
|
||
It's useful to know that what JavaScript calls arrays, Python calls lists. (Arrays and lists are basically the same, other than the name, though!) | ||
|
||
Googling for "Python filter list" suggests there are two things we can use - a `filter` function, or something called a "list comprehension". Some people prefer one, other people prefer the other. | ||
|
||
Using filter (`lambda` is a keyword for making an anonymous function in Python): | ||
|
||
```python | ||
filter(lambda word: args.char in word, content.split(" ")) | ||
``` | ||
|
||
Using a list comprehension: | ||
|
||
```python | ||
[word for word in content.split(" ") if args.char in word] | ||
``` | ||
|
||
Then we need to get the length of the produced list. Googling "python length of list" tells us we wrap our list in a call to `len()`, giving: | ||
|
||
```python | ||
len([word for word in content.split(" ") if args.char in word]) | ||
``` | ||
|
||
or | ||
|
||
```python | ||
len(filter(lambda word: args.char in word, content.split(" "))) | ||
``` | ||
|
||
The list comprehension version of this works. The `filter` version gives an error. We can try to understand and fix the error, or just use the list comprehension version. |
114 changes: 114 additions & 0 deletions
114
common-content/en/module/tools/first-nodejs-program/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
+++ | ||
title = "Writing a NodeJS program" | ||
headless = true | ||
time = 60 | ||
facilitation = false | ||
emoji= "🛠️" | ||
hide_from_overview = true | ||
objectives = [ | ||
"Write a zero-dependencies NodeJS program", | ||
] | ||
+++ | ||
|
||
Below we have a small NodeJS program. It is a bit like `wc`. It counts words in a file. Specifically, it counts words which contain a hyphen (`-`) character. | ||
|
||
It accepts one command line argument - the path of the file to read and count. | ||
|
||
Its output to stdout is just the number of words which contain a hyphen. | ||
|
||
It uses the same language (JavaScript) as we've written before, but uses some different APIs. | ||
|
||
```js | ||
import process from "node:process"; | ||
import { promises as fs } from "node:fs"; | ||
|
||
const argv = process.argv.slice(2); | ||
if (argv.length != 1) { | ||
console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); | ||
process.exit(1); | ||
} | ||
const path = argv[0]; | ||
|
||
const content = await fs.readFile(path, "utf-8"); | ||
const wordsContainingHyphens = content.split(" ").filter((word) => word.indexOf("-") > -1).length; | ||
console.log(wordsContainingHyphens); | ||
``` | ||
|
||
Let's play computer with this program - line by line: | ||
|
||
```js | ||
import process from "node:process"; | ||
``` | ||
|
||
This is loading some code from somewhere that isn't this file. | ||
|
||
We've seen `import` before. Here, instead of importing from a file we've written, we're importing the `process` module which is built into NodeJS. | ||
|
||
This is an example of the same language features (`import`) being used slightly differently (the `"node:"` is a special prefix to say "specially from node"). | ||
|
||
The `process` module is built into NodeJS for managing our process. It can be used to do things like find out what arguments were passed to the process when it started, find out what user ran the process, exit the process, and more. | ||
|
||
```js | ||
import { promises as fs } from "node:fs"; | ||
``` | ||
|
||
We're importing another module. | ||
|
||
The `fs` module is built into NodeJS for interacting with the filesystem. | ||
|
||
This time, we're not importing the whole module. We are destructuring. The `node:fs` module exposes an object, and we are saying "import the `promises` property from the `fs` module, and bind it to the name `fs`". | ||
|
||
It's the equivalent to us writing `import { promises } from "node:fs"; const fs = promises;`. | ||
|
||
We are doing this because many of the things in the `fs` module don't support `async`/`await`, but `fs` has a sub-module called `promises` where everything supports `async`/`await`. Because we want to use `async`/`await`, we will use that. But having to write `fs.promises.readFile` is a bit annoying, so instead we import `fs.promises` as if it was just named `fs`. | ||
|
||
```js | ||
const argv = process.argv.slice(2); | ||
``` | ||
|
||
We're getting the `argv` array from the `process` module, and slicing it. We can see in [the `process.argv` documentation](https://nodejs.org/api/process.html#processargv) that `process.argv[0]` will be the path to `node`, and `process.argv[1]` will be the path to this file. We don't care about those, so we'll skip them - as far as we're concerned the arguments start at index 2. | ||
|
||
Again, `Array.slice` is exactly the same as we know from JavaScript, but `process.argv` is a new API we can use to get the array we need. | ||
|
||
```js | ||
if (argv.length != 1) { | ||
console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); | ||
process.exit(1); | ||
} | ||
``` | ||
|
||
We always expect our program to be given exactly one argument. Here we check this using an `if` statement, just like we've seen before. | ||
|
||
`console.error` writes a message to stderr (which is where error messages should go). | ||
|
||
`process.exit` is a function which, when called, will stop our program running. Passing a non-zero number to it indicates that our program did not succeed. | ||
|
||
```js | ||
const path = argv[0]; | ||
``` | ||
|
||
Giving a useful name to our argument. | ||
|
||
```js | ||
const content = await fs.readFile(path, "utf-8"); | ||
``` | ||
|
||
Reading the file at the path passed as an argument. We're using the `fs` module here from `node`, but everything else is just JavaScript - declaring a variable, using `await` because `fs.promises.readFile` is an `async` function, calling a function. | ||
|
||
```js | ||
const wordsContainingHyphens = content.split(" ").filter((word) => word.indexOf("-") > -1).length; | ||
``` | ||
|
||
Just some regular JavaScript. Taking a string, splitting it into an array, filtering the array, searching strings to see if they contain characters, and getting the length of an array. | ||
|
||
```js | ||
console.log(wordsContainingHyphens); | ||
``` | ||
|
||
`console.log` in a NodeJS environment logs to stdout, so this outputs our result to stdout. | ||
|
||
{{<note type="Exercise">}} | ||
Save the above program into a file. Run the file with `node`, and count how many words contain hyphens in a few different files. | ||
|
||
If you run into problems, ask for help. | ||
{{</note>}} |
13 changes: 0 additions & 13 deletions
13
common-content/en/module/tools/implement-single-use-data-analysis/index.md
This file was deleted.
Oops, something went wrong.
13 changes: 0 additions & 13 deletions
13
common-content/en/module/tools/implement-tools-in-nodejs/index.md
This file was deleted.
Oops, something went wrong.
13 changes: 0 additions & 13 deletions
13
common-content/en/module/tools/implement-tools-in-python/index.md
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.