Add files via upload
This commit is contained in:
parent
c5cfdc373b
commit
b535f74f29
189
ipynb/DocstringFixpoint.ipynb
Normal file
189
ipynb/DocstringFixpoint.ipynb
Normal file
@ -0,0 +1,189 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4c9cc4a3-6e84-46be-9462-3ac9d12d7a61",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"text-align: right\" align=\"right\"><i>Peter Norvig<br>2023</i></div>\n",
|
||||
"\n",
|
||||
"# Docstring Fixpoint Theory\n",
|
||||
"\n",
|
||||
"This notebook makes the following proposal:\n",
|
||||
"\n",
|
||||
"- *One approach to writing the code for a function is to repeatedly edit the **docstring** and the function **code** until they converge to a **fixpoint** in which there is an obvious one-to-one correspondance between the two.*\n",
|
||||
"\n",
|
||||
"This approach follows the first of [Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)'s two methods: *\"There are two methods in software design. One is to make the program so simple, there are obviously no errors. The other is to make it so complicated, there are no obvious errors.\"* Some caveats: \n",
|
||||
"- This approach is not always appropriate! For many functions the docstring is a high-level description and the code has more detail that is not in the docstring.\n",
|
||||
"- The edits to the docstring must maintain the meaning (just change the expression).\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Example: The Rainfall Problem\n",
|
||||
"\n",
|
||||
"The \"Rainfall Problem\" has been used to explore the ways that novices address a programming problem. We will use [Kathi Fisler](https://cs.brown.edu/~kfisler)'s [version](https://cs.brown.edu/~kfisler/Pubs/icer14-rainfall/) of the problem:\n",
|
||||
"\n",
|
||||
"- Design a program called **rainfall** that consumes a list\n",
|
||||
"of numbers representing daily rainfall amounts as entered by a user. The list may contain the number -999\n",
|
||||
"indicating the end of the data of interest. Produce\n",
|
||||
"the average of the non-negative values in the list up to\n",
|
||||
"the first -999 (if it shows up). \n",
|
||||
"\n",
|
||||
"We start by writing a function prototype containing the complete problem statement as the docstring:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "4ac87ea7-7ad9-48e2-a21f-fb772e540487",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def rainfall(numbers: list):\n",
|
||||
" \"\"\"Design a program called rainfall that consumes a list of numbers \n",
|
||||
" representing daily rainfall amounts as entered by a user. \n",
|
||||
" The list may contain the number -999 indicating the end of the data of interest. \n",
|
||||
" Produce the average of the non-negative values in the list up to the first -999 \n",
|
||||
" (if it shows up). There may be negative numbers other than -999 in the list.\"\"\"\n",
|
||||
" ..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "493ba182-2d75-4caf-9998-c46637fd949f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We then edit the docstring to delete extraneous parts:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3000881a-f2b0-4e00-9bfa-9c358a94de2c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def rainfall(numbers):\n",
|
||||
" \"\"\"Produce the average of the non-negative values in a list of numbers,\n",
|
||||
" up to the first -999 (if it shows up).\"\"\"\n",
|
||||
" ..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "be7f6cc2-cd41-4472-8abf-f052a30bb031",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We then write code that mirrors the docstring as closely as possible:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 30,
|
||||
"id": "15bbdbb7-b16d-4347-80c1-bdac375a95b4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def rainfall(numbers: list):\n",
|
||||
" \"\"\"Produce the average of the non-negative values in a list of numbers,\n",
|
||||
" up to the first -999 (if it shows up).\"\"\"\n",
|
||||
" return mean(non_negative(upto(numbers, -999)))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "601a806a-f710-4877-851e-caff50657039",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And lightly edit the docstring once more:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 41,
|
||||
"id": "701bb096-d90e-4a45-bfbb-58ea2106fdd5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def rainfall(numbers: list) -> float:\n",
|
||||
" \"\"\"Return the mean of the non-negative values in a list of numbers,\n",
|
||||
" up to the first -999 (if it shows up).\"\"\"\n",
|
||||
" return mean(non_negative(upto(numbers, -999)))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6a1a566c-2113-4d71-a341-4a683d97e64a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now fill in the missing bits:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 42,
|
||||
"id": "dc686d52-2c8d-4e2b-be41-d8c354ef903e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from statistics import mean\n",
|
||||
"\n",
|
||||
"def upto(items: list, end) -> list:\n",
|
||||
" \"\"\"The items before the first occurence of `end` (if it shows up).\"\"\"\n",
|
||||
" return items if (end not in items) else items[:items.index(end)]\n",
|
||||
"\n",
|
||||
"def non_negative(numbers): return [x for x in numbers if x >= 0] "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dd04aa98-ef15-4288-bd70-e08d7d07d071",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And write some tests:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 43,
|
||||
"id": "9e6c81b8-1665-47cf-83f3-bf010bb6aa3d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def test():\n",
|
||||
" assert rainfall([3]) == 3, \"one day\"\n",
|
||||
" assert rainfall([0, 0]) == 0, \"no rain\"\n",
|
||||
" assert rainfall([1, 2, 3]) == 2, \"just the mean\"\n",
|
||||
" assert rainfall([1, 2, 3, 4]) == 2.5, \"just the mean (which is a non-integer)\"\n",
|
||||
" assert rainfall([1, 2, 3, 4, 0]) == 2, \"zero values are counted\"\n",
|
||||
" assert rainfall([1, 2, 3, 4, -100, 0]) == 2, \"negative values are ignored\"\n",
|
||||
" assert rainfall([1, 2, 3, -999, 404]) == 2, \"values after -999 are ignored\"\n",
|
||||
" \n",
|
||||
"test()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.15"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
Loading…
Reference in New Issue
Block a user