├── .gitattributes ├── .gitignore ├── Lectures ├── Lecture1 │ ├── practice.ipynb │ └── theory.ipynb ├── Lecture2 │ ├── practice.ipynb │ └── theory.ipynb ├── Lecture3 │ ├── practice.ipynb │ └── theory.ipynb ├── Lecture4 │ ├── practice.ipynb │ └── theory.ipynb ├── Lecture5 │ ├── practice.ipynb │ └── theory.ipynb ├── Lecture6 │ ├── practice.ipynb │ └── theory.ipynb ├── Lecture7 │ ├── practice.ipynb │ └── theory.ipynb └── Lecture8 │ ├── practice.ipynb │ ├── samples.py │ └── theory.ipynb ├── Reader ├── images │ ├── rectangle_to_rectangles.png │ ├── rectangle_to_row_rectangle.png │ ├── trapeze_to_trapezes.png │ └── triangle_to_row_triangle.png ├── lecture0.ipynb ├── lecture1.ipynb ├── lecture2.ipynb ├── lecture3.ipynb ├── lecture4.ipynb ├── lecture5.ipynb ├── lecture6.ipynb ├── lecture7.ipynb ├── lecture8.ipynb └── lecture9_conclusion.ipynb ├── modulewijzer.ipynb └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Core latex/pdflatex auxiliary files: 2 | *.aux 3 | *.lof 4 | *.log 5 | *.lot 6 | *.fls 7 | *.out 8 | *.toc 9 | 10 | ## Intermediate documents: 11 | *.dvi 12 | *-converted-to.* 13 | # these rules might exclude image files for figures etc. 14 | # *.ps 15 | # *.eps 16 | # *.pdf 17 | 18 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 19 | *.bbl 20 | *.bcf 21 | *.blg 22 | *-blx.aux 23 | *-blx.bib 24 | *.brf 25 | *.run.xml 26 | 27 | ## Build tool auxiliary files: 28 | *.fdb_latexmk 29 | *.synctex.gz 30 | *.synctex.gz(busy) 31 | *.pdfsync 32 | 33 | ## Auxiliary and intermediate files from other packages: 34 | 35 | # algorithms 36 | *.alg 37 | *.loa 38 | 39 | # amsthm 40 | *.thm 41 | 42 | # beamer 43 | *.nav 44 | *.snm 45 | *.vrb 46 | 47 | #(e)ledmac/(e)ledpar 48 | *.end 49 | *.[1-9] 50 | *.[1-9][0-9] 51 | *.[1-9][0-9][0-9] 52 | *.[1-9]R 53 | *.[1-9][0-9]R 54 | *.[1-9][0-9][0-9]R 55 | *.eledsec[1-9] 56 | *.eledsec[1-9]R 57 | *.eledsec[1-9][0-9] 58 | *.eledsec[1-9][0-9]R 59 | *.eledsec[1-9][0-9][0-9] 60 | *.eledsec[1-9][0-9][0-9]R 61 | 62 | # glossaries 63 | *.acn 64 | *.acr 65 | *.glg 66 | *.glo 67 | *.gls 68 | 69 | # hyperref 70 | *.brf 71 | 72 | # listings 73 | *.lol 74 | 75 | # makeidx 76 | *.idx 77 | *.ilg 78 | *.ind 79 | *.ist 80 | 81 | # minitoc 82 | *.maf 83 | *.mtc 84 | *.mtc0 85 | 86 | # minted 87 | *.pyg 88 | 89 | # morewrites 90 | *.mw 91 | 92 | # nomencl 93 | *.nlo 94 | 95 | # sagetex 96 | *.sagetex.sage 97 | *.sagetex.py 98 | *.sagetex.scmd 99 | 100 | # sympy 101 | *.sout 102 | *.sympy 103 | sympy-plots-for-*.tex/ 104 | 105 | # todonotes 106 | *.tdo 107 | 108 | # xindy 109 | *.xdy 110 | 111 | # ========================= 112 | # Operating System Files 113 | # ========================= 114 | 115 | # OSX 116 | # ========================= 117 | 118 | .DS_Store 119 | .AppleDouble 120 | .LSOverride 121 | 122 | # Thumbnails 123 | ._* 124 | 125 | # Files that might appear on external disk 126 | .Spotlight-V100 127 | .Trashes 128 | 129 | # Directories potentially created on remote AFP share 130 | .AppleDB 131 | .AppleDesktop 132 | Network Trash Folder 133 | Temporary Items 134 | .apdisk 135 | 136 | # Windows 137 | # ========================= 138 | 139 | # Windows image file caches 140 | Thumbs.db 141 | ehthumbs.db 142 | 143 | # Folder config file 144 | Desktop.ini 145 | 146 | # Recycle Bin used on file shares 147 | $RECYCLE.BIN/ 148 | 149 | # Windows Installer files 150 | *.cab 151 | *.msi 152 | *.msm 153 | *.msp 154 | 155 | # Windows shortcuts 156 | *.lnk 157 | 158 | *.lb 159 | 160 | *.ipynb_checkpoints* 161 | .vscode/settings.json 162 | -------------------------------------------------------------------------------- /Lectures/Lecture1/practice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Activities" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Note: the practice happens almost exclusively on paper, potentially augmented by multiple choice questions in order to submit an answer that shows understanding.\n", 15 | "\n", 16 | "The activities performed are:\n", 17 | "- define the steps of `eval` for various small programs featuring functions" 18 | ] 19 | } 20 | ], 21 | "metadata": { 22 | "kernelspec": { 23 | "display_name": "Python 3", 24 | "language": "python", 25 | "name": "python3" 26 | }, 27 | "language_info": { 28 | "codemirror_mode": { 29 | "name": "ipython", 30 | "version": 3 31 | }, 32 | "file_extension": ".py", 33 | "mimetype": "text/x-python", 34 | "name": "python", 35 | "nbconvert_exporter": "python", 36 | "pygments_lexer": "ipython3", 37 | "version": "3.5.2" 38 | } 39 | }, 40 | "nbformat": 4, 41 | "nbformat_minor": 1 42 | } 43 | -------------------------------------------------------------------------------- /Lectures/Lecture1/theory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Functional abstraction" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "_Note_: ensure that students copy, by hand and on paper, the various definitions written by the teacher on the whiteboard. It is strongly advised to ask students *not* to use a laptop, as it will prove distracting.\n", 15 | "\n", 16 | "The topics discussed are:\n", 17 | "- we now go back to our meta-language, so let us forget about Python for a moment\n", 18 | "- programming as giving (the right) names\n", 19 | "- variables as names of expressions\n", 20 | "- names as a problem specific taxonomy\n", 21 | " - fundamental elements of the problem also become fundamental elements of the solution\n", 22 | "- what about giving names to code?\n", 23 | " - hollow square example: the first and last line are duplicated\n", 24 | " - the issue with duplication is reasoning overhead, but even worse wrong level of abstraction: we are not mentioning the right concepts\n", 25 | "- in order to give a name to code, we need to be able to bind names to code\n", 26 | " - the state must be extended with a new sort of value: the \"code value\"\n", 27 | " - code values are known as _lambda expressions_\n", 28 | " - for our first attempt we will define a lambda as `<() => L>`, where `L` is a statement called _body_\n", 29 | " - notice the usual quotes around code, since we are treating code as an object passed around and inspected, just like in our $\\text{eval}$ function\n", 30 | " - our state will be able to contain code, in the format:\n", 31 | " \n", 32 | " $$\\{ x := 0; \\text{incr} := \\texttt{<() => x := x + 1>} \\}$$\n", 33 | " \n", 34 | " - we can then use code from the state by _invoking_ it\n", 35 | " - invocation has a simple syntax: `V()`, where `V` is the name of a variable bound to a lambda\n", 36 | " - the semantics of invocation are quite trivial: we simply replace the invocation statement with the code from the lambda:\n", 37 | " $\\text{eval}(, S) \\rightarrow , S$ where $S[V] \\rightarrow <() \\Rightarrow L>$\n", 38 | " - the combination of code in the state and the variable(s) to which the code is bound are collectively called _function_\n", 39 | "- let us see an example in depth, by following the steps of its evaluation:\n", 40 | "\n", 41 | "```\n", 42 | "x := 0\n", 43 | "y := 0\n", 44 | "incr_x := <() => x := x + 1>\n", 45 | "incr_y := <() => y := y + x>\n", 46 | "incr_x()\n", 47 | "incr_x()\n", 48 | "incr_y()\n", 49 | "```\n", 50 | "\n", 51 | "- notice that we can reuse the code for incrementing `x` without having to repeat it, and that `incr_x()` is less of a mouthful than `x := x + 1`\n", 52 | " - on a larger scale, this makes it harder to make mistakes\n", 53 | "- let us see a more complex example taken from our square-drawing from previous lectures\n", 54 | "- we would like to be able to specify that drawing a square requires drawing a line multiple times\n", 55 | "- this means we could redefine our sample as:\n", 56 | "\n", 57 | "```\n", 58 | "n := int(input())\n", 59 | "s := \"\"\n", 60 | "draw_line := <\n", 61 | " () =>\n", 62 | " x := n\n", 63 | " while x > 0 do\n", 64 | " s := s + \"*\"\n", 65 | " x := x - 1\n", 66 | " s := s + \"\\n\"\n", 67 | " >\n", 68 | "y := n\n", 69 | "while y > 0\n", 70 | " draw_line()\n", 71 | " y := y - 1\n", 72 | "```\n", 73 | "\n", 74 | "- this process of identifying interesting parts of our code and giving them a name is also known as _abstraction_\n", 75 | " - abstraction literally means \"removal\"\n", 76 | " - in our case, we remove details\n", 77 | " - saying `draw_line()` is far more direct and concise than its whole code\n", 78 | "- moreover, we can keep going in our process of abstraction until there is little left to abstract away:\n", 79 | "\n", 80 | "```\n", 81 | "n := int(input())\n", 82 | "s := \"\"\n", 83 | "draw_line := <\n", 84 | " () =>\n", 85 | " x := n\n", 86 | " while x > 0 do\n", 87 | " s := s + \"*\"\n", 88 | " x := x - 1\n", 89 | " s := s + \"\\n\"\n", 90 | " >\n", 91 | "draw_N_lines := <\n", 92 | " () =>\n", 93 | " y := n\n", 94 | " while y > 0\n", 95 | " draw_line()\n", 96 | " y := y - 1\n", 97 | " >\n", 98 | "\n", 99 | "draw_N_lines()\n", 100 | "```\n", 101 | "\n", 102 | "- in this new formulation we are simply saying that drawing a square is just the same as \"drawing N lines\"\n", 103 | " - \"drawing N lines\" does simply this: it \"draws a line\" `n` times\n", 104 | " - \"drawing a line\" simply draws `n` asterisks, followed by a `\"\\n\"` symbol\n", 105 | " - our program is now getting more readable, by hiding details behind (the names of) our functions\n", 106 | " - if we want to know what hides behind the name, we can simply go and check the function definition, that is the place in the program where the function variable was bound for the first time\n", 107 | "\n", 108 | "- sometimes, defining a function requires a little more context\n", 109 | "- for example, suppose we wanted to extend the definition of `incr_x` by also specifying by how much we want `x` to be incremented\n", 110 | " - this is trickier, because now invoking the function will also require defining this extra information\n", 111 | " - we call this extra information _parameter_ of the function, and we say the function is \"parameterized by it\"\n", 112 | " - we define a parameterized function as follows: `<(Pars) => L>`, where `L` is a statement and `Pars` is a series of variable names separated by comma\n", 113 | " - for example, we could now use the following assignment in a program: `incr_x_by := <(k) => x := x + k>`\n", 114 | " - the function `incr_x_by` will now increment `x` by `k`, which is a parameter of the function\n", 115 | " - invocation supplies _arguments_ to the function\n", 116 | " - the arguments are assigned to the parameters right before the function body is injected in the main program\n", 117 | " - this leads us to the following semantics:\n", 118 | " \n", 119 | " $\\text{eval}(, S) \\rightarrow , S$ where $S[V] \\rightarrow <(p_1, p_2, \\dots) \\Rightarrow L>$ and $a_1, a_2, \\dots$ are all constant values.\n", 120 | " \n", 121 | " Note that we cannot directly assign the arguments $a_1, a_2, ...$ to the parameters $p_1, p_2, ...$ inside the state, because the arguments could be expressions (for example, 3 + 1) instead of atomic values, so we need to execute the code to evaluate it. This means that, before the rule above can become active, we need to check if some of the arguments are not (yet) constant, in a left-to-right fashion:\n", 122 | " \n", 123 | " $\\text{eval}(, S) \\rightarrow <\\dots, E_i', \\dots; L>, S$ where $\\text{eval}(,S) \\rightarrow ,S'$ and $E_i$ is the first argument from the left which is not a constant.\n", 124 | " \n", 125 | "- as an example, consider the evaluation steps of the following program:\n", 126 | "\n", 127 | "```\n", 128 | "incr_x_by_k := <(k) => x := x + k>\n", 129 | "x := 0\n", 130 | "incr_x_by_k(1)\n", 131 | "incr_x_by_k(2)\n", 132 | "incr_x_by_k(x)\n", 133 | "```\n", 134 | "\n", 135 | "- notice that at each invocation we reassign `k` to the value between the brackets\n", 136 | "- we can also have multiple parameters in the function definition, and therefore multiple arguments when invoking the function\n", 137 | " - show a detailed expansion of the code below\n", 138 | "\n", 139 | "```\n", 140 | "mul_incr := <(c,k) => x := x * c + k>\n", 141 | "x := 1\n", 142 | "mul_incr(5, 3)\n", 143 | "mul_incr(2, 1)\n", 144 | "```\n", 145 | "\n" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": { 152 | "collapsed": true 153 | }, 154 | "outputs": [], 155 | "source": [] 156 | } 157 | ], 158 | "metadata": { 159 | "kernelspec": { 160 | "display_name": "Python 3", 161 | "language": "python", 162 | "name": "python3" 163 | }, 164 | "language_info": { 165 | "codemirror_mode": { 166 | "name": "ipython", 167 | "version": 3 168 | }, 169 | "file_extension": ".py", 170 | "mimetype": "text/x-python", 171 | "name": "python", 172 | "nbconvert_exporter": "python", 173 | "pygments_lexer": "ipython3", 174 | "version": "3.6.3" 175 | } 176 | }, 177 | "nbformat": 4, 178 | "nbformat_minor": 1 179 | } 180 | -------------------------------------------------------------------------------- /Lectures/Lecture2/practice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Activities" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Note: the practice happens almost exclusively on paper, potentially augmented by multiple choice questions in order to submit an answer that shows understanding.\n", 15 | "\n", 16 | "The activities performed are:\n", 17 | "- work with functions with local parameters overlapping with global parameters\n", 18 | "- work with functions calling long chains of other functions (non-recursively) without parameters\n", 19 | "- work with functions calling other functions with overlap between different local parameters and global parameters\n" 20 | ] 21 | } 22 | ], 23 | "metadata": { 24 | "kernelspec": { 25 | "display_name": "Python 3", 26 | "language": "python", 27 | "name": "python3" 28 | }, 29 | "language_info": { 30 | "codemirror_mode": { 31 | "name": "ipython", 32 | "version": 3 33 | }, 34 | "file_extension": ".py", 35 | "mimetype": "text/x-python", 36 | "name": "python", 37 | "nbconvert_exporter": "python", 38 | "pygments_lexer": "ipython3", 39 | "version": "3.5.2" 40 | } 41 | }, 42 | "nbformat": 4, 43 | "nbformat_minor": 1 44 | } 45 | -------------------------------------------------------------------------------- /Lectures/Lecture2/theory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Functional abstraction" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "_Note_: ensure that students copy, by hand and on paper, the various definitions written by the teacher on the whiteboard. It is strongly advised to ask students *not* to use a laptop, as it will prove distracting.\n", 15 | "\n", 16 | "The topics discussed are:\n", 17 | "- let us consider the following program\n", 18 | "```\n", 19 | "k := 0\n", 20 | "x := 0\n", 21 | "incr_x := x := x + k>\n", 22 | "incr_x(1)\n", 23 | "incr_x(3)\n", 24 | "...\n", 25 | "```\n", 26 | "\n", 27 | "- if we follow the semantics we have given in the previous lecture, then when we invoke `incr_x` for the first time we would redefine the value of `k`\n", 28 | " - this means that functions are abstracting code, but in a way that \"leaks\" into the context\n", 29 | " - leaks are very hard to manage, because they stop us from thinking about the function in isolation with respect to the rest of the program\n", 30 | " - moreover, we expect parameters such as `k` inside the function to have no relation with the parameters defined outside the function\n", 31 | "- for this reason we introduce the notion of _scope_\n", 32 | " - variables defined _inside_ a function only exist while the function is being called, and must then be removed from the state when the function is done\n", 33 | " - if these variables have the same name as other variables in the program, the other variables are left untouched\n", 34 | " - such variables are called _local_ (to the function), because they only exist inside the function and do not leak out of it\n", 35 | " - other variables are called _global_\n", 36 | "- scope is implemented, in the semantics, by adding yet another component to our state: the so-called _call stack_\n", 37 | " - the _call stack_ is a stack which will contain all the local variables of functions active at the current time\n", 38 | " - when a function is invoked, we add these values to the top of the stack\n", 39 | " - when the function is done, we remove the values from the top of the stack\n", 40 | "- we define a new instruction: `call`, in order to denote and delimit the invocation of a function\n", 41 | " - the state also contains a new entry, `stack`, where the local variables are pushed upon invocation\n", 42 | " - the stack is either empty $\\{ \\}$, or it contains a head $h$, with the local variables, and a tail $t$ with the rest of the stack\n", 43 | " - for example: $\\{ stack := \\{ h := \\{ x := 1 \\}, t := \\{ \\} \\}, x := 1 \\}$ is a state where variable `x` is defined both at the top of the stack and in the regular state\n", 44 | "- the new semantics of function invocation therefore becomes:\n", 45 | " \n", 46 | " $\\text{eval}(, S) \\rightarrow , S[\\text{stack} := \\{ h := \\{ p_1 := \\text{null}, p_2 := \\text{null}, \\dots \\}, t := s[\\text{stack}] \\}]$ where $S[V] \\rightarrow <(p_1, p_2, \\dots) \\Rightarrow L>$\n", 47 | " \n", 48 | "- let us consider only instruction `incr_x := x := x + k>; incr_x(1)` where the state is $\\{ k := 0, x := 0 \\}$\n", 49 | " - inside the body of the function, accessing `k` must now look in `S[stack][h][k]`, even though `S[k]` also exists\n", 50 | "- this means that variable lookup is now divided into two rules, in order to first look at the head of the stack and then, if nothing is found, in the globals:\n", 51 | " - $\\text{eval}(, S) \\rightarrow , S$, where `V` is a variable name\n", 52 | " - $\\text{eval}(, S) \\rightarrow , S$, where `V` is a variable name and $\\nexists S[\\text{stack}][h][V]$\n", 53 | " - this implies that we have removed `eval_expr`, and are now using `eval` for both statements and expressions\n", 54 | " - this has the important effect that evaluating expressions can now alter the state!\n", 55 | "- when a function terminates, then its body becomes `done` and we reach the combination `call(done)`: then we must \"pop the stack\", that is remove the last level of local variables from the stack in order to cleanup the state\n", 56 | " - $eval(, S) \\rightarrow , S[stack := S[stack][t]]$\n", 57 | "- let us see the original example, again, in depth, with the new lookup rules:\n", 58 | "\n", 59 | "```\n", 60 | "k := 0\n", 61 | "x := 0\n", 62 | "incr_x := x := x + k>\n", 63 | "incr_x(1)\n", 64 | "incr_x(3)\n", 65 | "...\n", 66 | "```\n", 67 | "\n", 68 | "- another example:\n", 69 | "\n", 70 | "```\n", 71 | "title := \"Mr.\"\n", 72 | "name := \"Strange\"\n", 73 | "add_title := name := title + \" \" + name>\n", 74 | "add_title(\"Dr.\")\n", 75 | "add_title(title)\n", 76 | "```\n", 77 | "\n", 78 | "- another example:\n", 79 | "\n", 80 | "```\n", 81 | "a := 0\n", 82 | "x := 0\n", 83 | "incr_x := <a => x := x + a>\n", 84 | "decr_x := <a => x := x - a>\n", 85 | "incr_x(10)\n", 86 | "decr_x(3)\n", 87 | "...\n", 88 | "```\n", 89 | "\n", 90 | "- with the stack, we can nest function calls arbitrarily: a function can call another function\n", 91 | "\n", 92 | "```\n", 93 | "x := 0\n", 94 | "double := <() => x := x + x>\n", 95 | "quadruple := <() => double(); double()>\n", 96 | "```\n", 97 | "\n", 98 | "```\n", 99 | "s := \"\"\n", 100 | "star := <() => s := s + \"*\">\n", 101 | "blank := <() => s := s + \" \">\n", 102 | "newline := <() => s := s + \"\\n\">\n", 103 | "line := <() => star(); star(); star(); newline() >\n", 104 | "square := <() => line(); line(); line() >\n", 105 | "square()\n", 106 | "```\n", 107 | "\n", 108 | "- even if some of these functions all use the same parameters, we still do not get clashes because each function stores its local variables in another location on the stack\n", 109 | "\n", 110 | "```\n", 111 | "k := 0\n", 112 | "x := 0\n", 113 | "incr_x := <k => x := x + k>\n", 114 | "mult_x := <k => x := x * k>\n", 115 | "mult_incr_x := <(k,c) => mult_x(k); incr_x(c)>\n", 116 | "mult_incr_x(2,3)\n", 117 | "...\n", 118 | "```\n", 119 | "\n", 120 | "- the block of code in which a variable is active is called its _scope_\n", 121 | "- for example, in the code we just discussed above, there are four different scopes for `k`\n" 122 | ] 123 | } 124 | ], 125 | "metadata": { 126 | "kernelspec": { 127 | "display_name": "Python 3", 128 | "language": "python", 129 | "name": "python3" 130 | }, 131 | "language_info": { 132 | "codemirror_mode": { 133 | "name": "ipython", 134 | "version": 3 135 | }, 136 | "file_extension": ".py", 137 | "mimetype": "text/x-python", 138 | "name": "python", 139 | "nbconvert_exporter": "python", 140 | "pygments_lexer": "ipython3", 141 | "version": "3.5.2" 142 | } 143 | }, 144 | "nbformat": 4, 145 | "nbformat_minor": 1 146 | } 147 | -------------------------------------------------------------------------------- /Lectures/Lecture3/practice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Activities" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Note: the practice happens almost exclusively on paper, potentially augmented by multiple choice questions in order to submit an answer that shows understanding.\n", 15 | "\n", 16 | "The activities performed are:\n", 17 | "\n", 18 | "- work with functions returning values\n", 19 | "- work with nested functions returning values\n", 20 | "- work with expressions containing functions\n", 21 | "- work with functions with argument expressions containing functions (`f(g(x))`)\n" 22 | ] 23 | } 24 | ], 25 | "metadata": { 26 | "kernelspec": { 27 | "display_name": "Python 3", 28 | "language": "python", 29 | "name": "python3" 30 | }, 31 | "language_info": { 32 | "codemirror_mode": { 33 | "name": "ipython", 34 | "version": 3 35 | }, 36 | "file_extension": ".py", 37 | "mimetype": "text/x-python", 38 | "name": "python", 39 | "nbconvert_exporter": "python", 40 | "pygments_lexer": "ipython3", 41 | "version": "3.5.2" 42 | } 43 | }, 44 | "nbformat": 4, 45 | "nbformat_minor": 1 46 | } 47 | -------------------------------------------------------------------------------- /Lectures/Lecture3/theory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Returning values and referential transparency" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "_Note_: ensure that students copy, by hand and on paper, the various definitions written by the teacher on the whiteboard. It is strongly advised to ask students *not* to use a laptop, as it will prove distracting.\n", 15 | "\n", 16 | "The topics discussed are:\n", 17 | "- functions writing to the same value can produce a clash\n", 18 | "- `k => x := x + k` clashes with `k => x := x * k`\n", 19 | "- even worse, consider drawing a hollow square \n", 20 | "- we might want to define a few isolated, reusable concepts such as \"draw a full line\" and \"draw a hollow line\":\n", 21 | "\n", 22 | "```\n", 23 | "draw_full_line := () => s := \"*****\"\n", 24 | "draw_hollow_line := () => s := \"* *\"\n", 25 | "...\n", 26 | "```\n", 27 | "\n", 28 | "- at this point we might want to also define the function for drawing the whole hollow square, without thinking about drawing individual lines anymore (the advantage of functions: thinking about different concepts in isolation):\n", 29 | "\n", 30 | "```\n", 31 | "...\n", 32 | "\n", 33 | "draw_square := () =>\n", 34 | " s := \"\"\n", 35 | " draw_full_line()\n", 36 | " s := s + ?\n", 37 | " draw_hollow_line()\n", 38 | " s := s + ?\n", 39 | " draw_hollow_line()\n", 40 | " s := s + ?\n", 41 | " draw_hollow_line()\n", 42 | " s := s + ?\n", 43 | " draw_full_line()\n", 44 | " s := s + ?\n", 45 | "```\n", 46 | "\n", 47 | "- the problem we encounter here is that we want to put the result into variable `s`, but we cannot!\n", 48 | " - because `s` is also used by the line-drawing functions, it gets reset everytime!\n", 49 | "- this breaks the isolation (also known as **encapsulation**) of functions\n", 50 | "- invoking a function requires us to carefully think about how it works internally, and what variables it writes to\n", 51 | " - this is possible, but clearly inconvenient\n", 52 | " - a good programming language must adjust to logical concepts, not the other way around" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "## Returning\n", 60 | "- what we are looking for is a mechanism that allows functions to produce an output without having to rely on shared global variables\n", 61 | " - because the variables are global, sharing them will produce some conflicts, which are considered to fall in the broad category of dangerous constructs known as _data races_ or _race conditions_\n", 62 | " - if each function has its own safe place to store the output, then the act of returning a result will be intrinsically isolated from all other functions\n", 63 | " - this increases isolation, so it is a positive property\n", 64 | "- we introduce the new statement, `return`\n", 65 | "- the syntax is `return E`, where `E` is any expression (also another function call!)\n", 66 | "- the semantics simply evaluates the expression until it is a constant value:\n", 67 | " - $\\text{eval(<return C>, S)} \\rightarrow \\text{<C>, S}$ when $\\text{C}$ is a (constant) value\n", 68 | " - $\\text{eval(<return E>, S)} \\rightarrow \\text{<return E'>, S'}$ where $\\text{eval(<E>, S)} \\rightarrow \\text{<E'>, S'}$\n", 69 | "- the fact that a function might result in something else other than just the `done` statement means that we also have to adjust the semantics of `call`:\n", 70 | " - $\\text{eval(<call(return C)>, S)} \\rightarrow \\text{<C>, S[stack := S[stack][t]]}$ when $\\text{C}$ is a (constant) value\n", 71 | " - $\\text{eval(<call(return C; ...)>, S)} \\rightarrow \\text{<C>, S[stack := S[stack][t]]}$ when $\\text{C}$ is a (constant) value\n", 72 | " - $\\text{eval(<call(P)>, S)} \\rightarrow \\text{<call(P')>, S'}$ where $\\text{eval(<P>, S)} \\rightarrow \\text{<P'>, S'}$\n", 73 | "- notice that thanks to the second rule for `call`, we are ignoring the rest of the function when we return\n", 74 | " - this is an important property of `return`, as it \"short-circuits\" a function and closes it right away\n", 75 | "- thanks to `return`, writing to global variables is not needed anymore\n", 76 | "- communication between a function and the calling context is done via `return`, not writing to some shared global\n", 77 | " - we can read first the locals and then the globals\n", 78 | " - we always write locals, even if globals are present" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "## Examples of functions using `return`\n", 86 | "- let us consider some simple examples of drawing functions that make use of the `return` statement\n", 87 | "- each function will simply return what it was meant to draw, instead of writing it to some arbitrary variable\n", 88 | "\n", 89 | "- the first example will be drawing a 4x4 hollow square:\n", 90 | " - let us begin with drawing single characters such as an asterisk or a newline: \n", 91 | " - `asterisk := () => return \"*\"`\n", 92 | " - `space := () => return \" \"`\n", 93 | " - `new_line := () => return \"\\n\"`\n", 94 | " - now we can combine these results together:\n", 95 | " - `full_line := () => return asterisk() + asterisk() + asterisk() + asterisk() + new_line()`\n", 96 | " - `hollow_line := () => return asterisk() + space() + space() + asterisk() + new_line()`\n", 97 | " - the final result is quite elegant:\n", 98 | " - `hollow_square := () => return full_line() + hollow_line() + hollow_line() + full_line()`\n", 99 | "\n", 100 | " - [[STATE TRACE OF PROGRAM (quite long!)]]\n", 101 | "\n", 102 | " - notice that in this example we have not been needing variables anywhere!\n", 103 | " \n", 104 | "- let us now generalize our functions so that they also accept the size of the figure as input\n", 105 | " - the basic functions for drawing single characters stay exactly the same\n", 106 | " - drawing a full line now accepts the length of the line as a parameter:\n", 107 | " - `full_line := n => l := \"\"; while n > 0: { n := n - 1; l := l + asterisk() }; return l + new_line()`\n", 108 | " - drawing a hollow line also accepts the length of the line as a parameter:\n", 109 | " - `hollow_line := n => l := \"\"; while n > 2: { n := n - 1; l := l + space() }; return asterisk() + l + asterisk() + new_line()`\n", 110 | "- armed with the certainty that each of these functions works in full isolation from the rest, we can compose them together to build the final figure:\n", 111 | " - `hollow_square := n => l := full_line(n); while n > 2: { n := n - 1; l := l + hollow_line(n) }; return l + full_line(n)`\n", 112 | " - [[STATE TRACE OF PROGRAM]]\n", 113 | " \n", 114 | "- we could also use the `full_line` function also to implement a (reversed) triangle, for example:\n", 115 | " - `triangle := n => l := \"\"; while n > 0: { l := l + full_line(n); n := n - 1 }; return l`\n", 116 | " - [[STATE TRACE OF PROGRAM]]" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "## Functions in Python\n", 124 | "- we can translate the constructs we have seen so far into Python \n", 125 | " - unfortunately Python offers a slightly more complex view of functions than the one we presented so far\n", 126 | "- Python allows us to define lambda expressions and assign them to variables, but unfortunately Python has a very limited definition of lambda expressions\n", 127 | "- Moreover, if we want to use full lambda expressions, then we need to assign them to a constant (a variable that can only be assigned once)\n", 128 | " - this is done with `def` and `return`\n", 129 | "- for example, we could define a function to draw a line of asterisks as:" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 2, 135 | "metadata": {}, 136 | "outputs": [ 137 | { 138 | "name": "stdout", 139 | "output_type": "stream", 140 | "text": [ 141 | "*****\n" 142 | ] 143 | } 144 | ], 145 | "source": [ 146 | "def draw_line(n):\n", 147 | " l = \"\"\n", 148 | " while n > 0:\n", 149 | " l = l + \"*\"\n", 150 | " n = n - 1\n", 151 | " return l\n", 152 | "print(draw_line(5))" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "metadata": {}, 158 | "source": [ 159 | "- Notice that Python combines defining and naming the function\n", 160 | "- Lambda expressions can also be defined in Python, but unfortunately they will directly return the only expression in their body\n", 161 | " - This greatly limits their usefulness, as they cannot contain multiple statements" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 3, 167 | "metadata": {}, 168 | "outputs": [ 169 | { 170 | "name": "stdout", 171 | "output_type": "stream", 172 | "text": [ 173 | "11\n" 174 | ] 175 | } 176 | ], 177 | "source": [ 178 | "f = lambda x: x + 1\n", 179 | "print(f(10))" 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "metadata": {}, 185 | "source": [ 186 | "## Functions inside expressions\n", 187 | "- Consider now the following statement: `x := f(3) + g(2)`\n", 188 | "- It contains two function calls one after the other\n", 189 | "- As you might recall, the first time we tackled the issue of evaluating expressions we introduced a simplifying hypothesis: that expression evaluation does not change the state\n", 190 | "- This hypothesis must now be lifted: expression evaluation has no special status, and can change the state\n", 191 | "- For this reason, all `eval_expr` rules that we defined so far are now \"lifted\" into `eval`, that is they may return a changed state instead of only the new expression for further computation\n", 192 | "- As an example, consider the rules for evaluating a sum:\n" 193 | ] 194 | } 195 | ], 196 | "metadata": { 197 | "kernelspec": { 198 | "display_name": "Python 3", 199 | "language": "python", 200 | "name": "python3" 201 | }, 202 | "language_info": { 203 | "codemirror_mode": { 204 | "name": "ipython", 205 | "version": 3 206 | }, 207 | "file_extension": ".py", 208 | "mimetype": "text/x-python", 209 | "name": "python", 210 | "nbconvert_exporter": "python", 211 | "pygments_lexer": "ipython3", 212 | "version": "3.5.2" 213 | } 214 | }, 215 | "nbformat": 4, 216 | "nbformat_minor": 1 217 | } 218 | -------------------------------------------------------------------------------- /Lectures/Lecture4/practice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Activities" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Note: the practice happens almost exclusively on paper, potentially augmented by multiple choice questions in order to submit an answer that shows understanding.\n", 15 | "\n", 16 | "The activities performed are:\n", 17 | "\n", 18 | "- work with the analysis and completion of simple recursive functions\n", 19 | " - sum of numbers\n", 20 | " - factorial\n", 21 | " - product-as-sums\n", 22 | " - power" 23 | ] 24 | } 25 | ], 26 | "metadata": { 27 | "kernelspec": { 28 | "display_name": "Python 3", 29 | "language": "python", 30 | "name": "python3" 31 | }, 32 | "language_info": { 33 | "codemirror_mode": { 34 | "name": "ipython", 35 | "version": 3 36 | }, 37 | "file_extension": ".py", 38 | "mimetype": "text/x-python", 39 | "name": "python", 40 | "nbconvert_exporter": "python", 41 | "pygments_lexer": "ipython3", 42 | "version": "3.5.2" 43 | } 44 | }, 45 | "nbformat": 4, 46 | "nbformat_minor": 1 47 | } 48 | -------------------------------------------------------------------------------- /Lectures/Lecture4/theory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Recursion" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "_Note_: ensure that students copy, by hand and on paper, the various definitions written by the teacher on the whiteboard. It is strongly advised to ask students *not* to use a laptop, as it will prove distracting.\n", 15 | "\n", 16 | "The topics discussed are:\n", 17 | "- we will now focus on a very powerful way of solving complex problems _without_ loops\n", 18 | "- let us consider a line of asterisks of length `n`\n", 19 | "- a line can be seen as the concatenation of smaller lines:\n", 20 | " - $\\texttt{\"*\"} + \\underbrace{\\texttt{\"*...*\"}}_{n-1}$\n", 21 | " - $\\underbrace{\\texttt{\"*...*\"}}_{n/2} + \\underbrace{\\texttt{\"*...*\"}}_{n/2}$ (if $n$ is even)\n", 22 | " - $\\texttt{\"*\"} + \\underbrace{\\texttt{\"*...*\"}}_{n/2} + \\underbrace{\\texttt{\"*...*\"}}_{n/2}$ (if $n$ is odd)\n", 23 | " - etc.\n", 24 | "- a line of length $0$ does not require further thought: it is just the empty string \n", 25 | "- to recap: \n", 26 | " - a line of length $n$ is:\n", 27 | " - an empty string when $n = 0$\n", 28 | " - a composition of an asterisk and a line of length $n-1$ otherwise\n", 29 | "- line clearly becomes a function, with a single parameter `n`\n", 30 | "- let us take a step towards code:\n", 31 | "\n", 32 | "```\n", 33 | "line = n =>\n", 34 | " if n = 0 then\n", 35 | " return \"\"\n", 36 | " else\n", 37 | " rest = line(n-1)\n", 38 | " return \"*\" + rest\n", 39 | "```\n", 40 | "\n", 41 | "- that's it, this works already!" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 5, 47 | "metadata": {}, 48 | "outputs": [ 49 | { 50 | "name": "stdout", 51 | "output_type": "stream", 52 | "text": [ 53 | "8\n", 54 | "********\n" 55 | ] 56 | } 57 | ], 58 | "source": [ 59 | "def line(n):\n", 60 | " if n == 0:\n", 61 | " return \"\"\n", 62 | " else:\n", 63 | " rest = line(n-1)\n", 64 | " return \"*\" + rest\n", 65 | "print(line(int(input())))" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "- let us now see the state trace\n", 73 | " - [[STATE TRACE]]\n", 74 | " - the stack grows, creating one entry per active function (thus per asterisk to be added)\n", 75 | " - when a function returns, then we add its value to the return expression, which grows \"right-to-left\"\n", 76 | "\n", 77 | "- we can also use a different decomposition, for example in half:\n", 78 | "\n", 79 | "```\n", 80 | "line = n =>\n", 81 | " if n = 0 then\n", 82 | " return \"\"\n", 83 | " else\n", 84 | " half = line(n//2)\n", 85 | " extra = if n % 2 = 0 then \"\" else \"*\"\n", 86 | " return half + half + extra\n", 87 | "```\n", 88 | "\n", 89 | "- now it takes less stack levels, because every step we divide the line in two instead of just removing one level" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 4, 95 | "metadata": {}, 96 | "outputs": [ 97 | { 98 | "name": "stdout", 99 | "output_type": "stream", 100 | "text": [ 101 | "7\n", 102 | "*******\n" 103 | ] 104 | } 105 | ], 106 | "source": [ 107 | "def line(n):\n", 108 | " if n == 0:\n", 109 | " return \"\"\n", 110 | " else:\n", 111 | " half = line(n//2)\n", 112 | " extra = \"\" if n % 2 == 0 else \"*\" \n", 113 | " return half + half + extra\n", 114 | "print(line(int(input())))" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "metadata": {}, 120 | "source": [ 121 | "- let us now see the state trace\n", 122 | " - [[STATE TRACE]]\n", 123 | " - the stack grows, creating one entry per active function\n", 124 | " - since we divide the length by two every time, it takes a lot less total steps\n", 125 | " - when a function returns, then we add its value to the return expression, which doubles at each level" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "## General idea of the process\n", 133 | "- the general process we are following is called \"recursion\", or (in mathematics) \"induction\"\n", 134 | " - state the problem: _drawing a line of `n` characters_\n", 135 | " - divide the problem into similar, smaller problems (the _inductive_ case)\n", 136 | " - a line is made up of smaller lines\n", 137 | " - identify an end case that is easy to identify and solve (the _base_ case)\n", 138 | " - a line of $0$ characters is the empty string `\"\"`\n", 139 | " - define a function to solve the problem: `line = n => ...`\n", 140 | " - check the input of the function to see if we are dealing with the end case or not:\n", 141 | " - `if n = 0 then \"\" else ...`\n", 142 | " - in the `else` branch, invoke the function recursively with adjusted parameters (**closer to the base case**):\n", 143 | " - `rest = line(n-1)`\n", 144 | " - assemble the partial results in the final result and return it\n", 145 | " - `return \"*\" + rest`\n", 146 | "- following our template, a recursive function typically looks as follows:\n", 147 | "\n", 148 | "\n", 149 | "```\n", 150 | "f = args =>\n", 151 | " if simple_to_solve then\n", 152 | " return base_solution\n", 153 | " else\n", 154 | " part1 := f(args1)\n", 155 | " part2 := f(args2)\n", 156 | " ...\n", 157 | " partN := f(argsN)\n", 158 | " return combine_parts\n", 159 | "```\n", 160 | "\n", 161 | "- in the case of drawing lines:\n", 162 | " - `f` becomes `line`\n", 163 | " - `args` becomes `n`\n", 164 | " - `simple_to_solve` becomes `n = 0` (or `n <= 0` to also gracefully capture nonsensical calls such as `line(-3)`)\n", 165 | " - `base_solution` becomes `\"\"`\n", 166 | " - `part1` becomes `\"*\"`\n", 167 | " - `part2` becomes `rest`\n", 168 | " - `f(args2)` becomes `line(n-1)`\n", 169 | " - `combine_parts` becomes `part1 + part2`" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "## Power of recursion\n", 177 | "- the question now becomes: is recursion more or less (or equally) powerful as loops?\n", 178 | "- to answer this, let us begin with a simple loop that sums the numbers from $1$ to $n$\n", 179 | "\n", 180 | "```\n", 181 | "n := int(input())\n", 182 | "sum := 0\n", 183 | "while n > 0 do\n", 184 | " sum := sum + n\n", 185 | " n := n - 1\n", 186 | "print(sum)\n", 187 | "```\n", 188 | "\n", 189 | "- we could also rephrase the problem in recursive terms\n", 190 | " - the sum of all numbers $1 \\dots n$ is:\n", 191 | " - $0$ if $n = 0$\n", 192 | " - the sum of all numbers $1 \\dots n-1$ plus $n$ otherwise\n", 193 | "- this leads us to the following implementation:\n", 194 | "\n", 195 | "```\n", 196 | "sum_numbers_to = n =>\n", 197 | " if n <= 0 then\n", 198 | " return 0\n", 199 | " else\n", 200 | " rest := sum_numbers_to(n-1)\n", 201 | " return rest + n\n", 202 | " \n", 203 | "sum := sum_numbers_to(int(input())\n", 204 | "print(sum)\n", 205 | "```\n", 206 | "\n", 207 | "- at a first glance, the two implementations look different, yet comparable\n", 208 | "- let us see if we can bridge the gap between them\n", 209 | "- the first line before the `while` loop, `sum := 0`, is the base case (`BASE`)\n", 210 | "- the condition `n > 0` identifies when the base case _is not already met_ and we must proceed with further computation (`COND`)\n", 211 | "- the body of the loop contains the argument to the recursive call `n-1` (`REC`)\n", 212 | "- the body of the loop also contains the composition of the final result (`COMP`)\n", 213 | "\n", 214 | "- we can now restate both versions of the program, but the elements of code for their names\n", 215 | "\n", 216 | "- the loop becomes now:\n", 217 | "```\n", 218 | "n := int(input())\n", 219 | "sum := BASE\n", 220 | "while COND do\n", 221 | " sum := COMP\n", 222 | " n := REC\n", 223 | "``` \n", 224 | "\n", 225 | "- the recursive version becomes:\n", 226 | "```\n", 227 | "sum_numbers_to = n =>\n", 228 | " if not COND then\n", 229 | " return BASE\n", 230 | " else\n", 231 | " rest := sum_numbers_to(REC)\n", 232 | " return COMP\n", 233 | "```\n", 234 | "\n", 235 | "- since virtually all individual loops can be condensed to a format such as the above (minus some minor adjustments), then we know that `while`, as a language construct, can be fully emulated with a recursive function\n", 236 | "- we can now check whether or not the opposite is the case as well\n", 237 | "\n", 238 | "- we can convert a recursive function with the same structure as the one above to a loop, but there are other recursive functions that are not really easy to translate\n", 239 | "- let us for example consider the Fibonacci function, which models the growth of a population of rabbits:\n", 240 | "\n", 241 | "```\n", 242 | "fib = n => if n <= 1 then return n else return fib(n-1) + fib(n-2)\n", 243 | "```\n", 244 | "\n", 245 | "- [[STATE TRACE]]\n", 246 | "- the stack is used very intensively, because every level adds two fully recursive calls\n", 247 | "- the program also grows a lot in size, because of all the intermediate steps that are frozen in place\n", 248 | " - because of its huge size, this formulation cannot be directly translated into a loop (unless we add a global variable storing a stack, but then we are simply rebuilding recursive functions!)\n", 249 | " - a loop \"grows\" linearly in complexity, a recursive function exponentially!\n", 250 | "- the power of recursion makes it a fundamental tool in the toolbox of a modern developer!" 251 | ] 252 | } 253 | ], 254 | "metadata": { 255 | "kernelspec": { 256 | "display_name": "Python 3", 257 | "language": "python", 258 | "name": "python3" 259 | }, 260 | "language_info": { 261 | "codemirror_mode": { 262 | "name": "ipython", 263 | "version": 3 264 | }, 265 | "file_extension": ".py", 266 | "mimetype": "text/x-python", 267 | "name": "python", 268 | "nbconvert_exporter": "python", 269 | "pygments_lexer": "ipython3", 270 | "version": "3.5.2" 271 | } 272 | }, 273 | "nbformat": 4, 274 | "nbformat_minor": 1 275 | } 276 | -------------------------------------------------------------------------------- /Lectures/Lecture5/practice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Activities" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Note: the practice happens almost exclusively on paper, potentially augmented by multiple choice questions in order to submit an answer that shows understanding.\n", 15 | "\n", 16 | "The activities performed are:\n", 17 | "\n", 18 | "- work with the analysis and completion of simple recursive functions\n", 19 | " - sum of 1..n\n", 20 | " - product of 1..n\n", 21 | " - multiplication as repeated addition\n", 22 | " - power as repeated multiplication\n", 23 | " - line (asterisk + line, line + asterisk + line, line + line(n-n//2))\n", 24 | " - square (rectangle, various formulations: per line, rectangle as sum of rectangles, etc.)\n", 25 | " - triangle (trapezoid)" 26 | ] 27 | } 28 | ], 29 | "metadata": { 30 | "kernelspec": { 31 | "display_name": "Python 3", 32 | "language": "python", 33 | "name": "python3" 34 | }, 35 | "language_info": { 36 | "codemirror_mode": { 37 | "name": "ipython", 38 | "version": 3 39 | }, 40 | "file_extension": ".py", 41 | "mimetype": "text/x-python", 42 | "name": "python", 43 | "nbconvert_exporter": "python", 44 | "pygments_lexer": "ipython3", 45 | "version": "3.5.2" 46 | } 47 | }, 48 | "nbformat": 4, 49 | "nbformat_minor": 1 50 | } 51 | -------------------------------------------------------------------------------- /Lectures/Lecture5/theory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Designing recursive functions" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "_Note_: ensure that students copy, by hand and on paper, the various definitions written by the teacher on the whiteboard. It is strongly advised to ask students *not* to use a laptop, as it will prove distracting.\n", 15 | "\n", 16 | "The topics discussed are:\n", 17 | "- we will now suspend the \"theory\" for a lecture in order to see a series of examples of recursive functions\n", 18 | "- general process:\n", 19 | " - draw the problem fully\n", 20 | " - find the recurring structures\n", 21 | " - combine the recurring structures together\n", 22 | " - find the base case\n", 23 | " - find a discriminator between base case and recurring structure\n", 24 | "- adding numbers 1..n\n", 25 | "- multiplying numbers 1..n\n", 26 | "- multiplication as repeated addition\n", 27 | "- power as repeated multiplication\n", 28 | "- line\n", 29 | " - asterisk + line\n", 30 | " - line + asterisk + line\n", 31 | "- rectangle\n", 32 | " - line + rectangle\n", 33 | " - rectangle + rectangle\n", 34 | " - rectangle + rectangle along the longest axis\n", 35 | "- triangle\n", 36 | " - triangle + line\n", 37 | " - trapezoid + trapezoid" 38 | ] 39 | } 40 | ], 41 | "metadata": { 42 | "kernelspec": { 43 | "display_name": "Python 3", 44 | "language": "python", 45 | "name": "python3" 46 | }, 47 | "language_info": { 48 | "codemirror_mode": { 49 | "name": "ipython", 50 | "version": 3 51 | }, 52 | "file_extension": ".py", 53 | "mimetype": "text/x-python", 54 | "name": "python", 55 | "nbconvert_exporter": "python", 56 | "pygments_lexer": "ipython3", 57 | "version": "3.5.2" 58 | } 59 | }, 60 | "nbformat": 4, 61 | "nbformat_minor": 1 62 | } 63 | -------------------------------------------------------------------------------- /Lectures/Lecture6/practice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Activities" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Note: the practice happens almost exclusively on paper, potentially augmented by multiple choice questions in order to submit an answer that shows understanding.\n", 15 | "\n", 16 | "The activities performed are:\n", 17 | "\n", 18 | "- working with higher order functions and partial application\n", 19 | " - add : int -> int -> int\n", 20 | " - mul : int -> int -> int\n", 21 | " - repeat : (a -> a, int) -> (a -> a)\n", 22 | " - repeat : (a -> a) -> int -> (a -> a)\n", 23 | " - incr, double\n", 24 | " - then \n", 25 | " - drawing with repeat: asterisk, line, square" 26 | ] 27 | } 28 | ], 29 | "metadata": { 30 | "kernelspec": { 31 | "display_name": "Python 3", 32 | "language": "python", 33 | "name": "python3" 34 | }, 35 | "language_info": { 36 | "codemirror_mode": { 37 | "name": "ipython", 38 | "version": 3 39 | }, 40 | "file_extension": ".py", 41 | "mimetype": "text/x-python", 42 | "name": "python", 43 | "nbconvert_exporter": "python", 44 | "pygments_lexer": "ipython3", 45 | "version": "3.5.2" 46 | } 47 | }, 48 | "nbformat": 4, 49 | "nbformat_minor": 1 50 | } 51 | -------------------------------------------------------------------------------- /Lectures/Lecture6/theory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Higher order functions and partial application" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "_Note_: ensure that students copy, by hand and on paper, the various definitions written by the teacher on the whiteboard. It is strongly advised to ask students *not* to use a laptop, as it will prove distracting.\n", 15 | "\n", 16 | "The topics discussed are:\n", 17 | "- our broader goal is to achieve higher and higher levels of abstraction in our code\n", 18 | " - we want to define computational concepts related to our domain _precisely and succintly_\n", 19 | " - this way we can *say more with less*, reducing both effort (mental cost) and chance of mistakes\n", 20 | "- we can start by observing two apparently unrelated functions, and still look for similarity between them:\n", 21 | "\n", 22 | "```\n", 23 | "sum := n =>\n", 24 | " s := 0\n", 25 | " while n > 0:\n", 26 | " s := s + n\n", 27 | " n := n - 1\n", 28 | " return s\n", 29 | "\n", 30 | "line := n =>\n", 31 | " s := \"\"\n", 32 | " while n > 0:\n", 33 | " s := s + \"*\"\n", 34 | " n := n - 1\n", 35 | " return s\n", 36 | "```\n", 37 | "\n", 38 | "- of course the two samples are really different, but it is clear that they also share a lot of structure\n", 39 | "- let us rewrite them by \"abstracting out\" the differing bits:\n", 40 | "\n", 41 | "```\n", 42 | "sum := n =>\n", 43 | " s := BASE\n", 44 | " while n > 0:\n", 45 | " s := s + STEP(n)\n", 46 | " n := n - 1\n", 47 | " return s\n", 48 | "\n", 49 | "line := n =>\n", 50 | " s := BASE\n", 51 | " while n > 0:\n", 52 | " s := s + STEP(n)\n", 53 | " n := n - 1\n", 54 | " return s\n", 55 | "```\n", 56 | "\n", 57 | "- notice that both formulations are now dependent on two extra \"things\", which we can just define as parameters\n", 58 | " - one of the arguments, `BASE`, is simply a value, so we turn it into an argument\n", 59 | " - the other argument, `STEP`, is a function, but this is no problem: a function can easily be an argument as well!\n", 60 | "- **functions are values just like `3` or `5`, and so they can be passed around as parameters, but also returned as results from functions!**\n", 61 | "- this leads us to the following definition, generic wrt:\n", 62 | " - the starting value, `BASE`\n", 63 | " - what is added at each step `STEP`, which needs to know which step we are talking about\n", 64 | "\n", 65 | "```\n", 66 | "repeat := (n,BASE,STEP) =>\n", 67 | " s := BASE\n", 68 | " while n > 0:\n", 69 | " s := s + STEP(n)\n", 70 | " n := n - 1\n", 71 | " return s\n", 72 | "```\n", 73 | "\n", 74 | "- we call `repeat` a higher order function (HOF), since it takes functions as parameters\n", 75 | "- we can now instantiate this function twice in order to obtain `sum` and `line`:\n", 76 | "\n", 77 | "```\n", 78 | "sum := n => return repeat(n,0,x => return x)\n", 79 | "line := n => return repeat(n,\"\",x => return \"*\")\n", 80 | "```\n", 81 | "\n", 82 | "- `sum` will simply add each value from `n` down to `1`, starting from `0`\n", 83 | "- `line` is almost the same: it adds `n` asterisks, starting from the empty string `\"\"`\n", 84 | "\n", 85 | "- both `sum` and `line`, according to the original definition, must take only `n` as a parameter\n", 86 | " - therefore, they both accept `n` as input and pass it through to `repeat` (this is not ideal, and we will improve on this concept later)\n", 87 | "\n", 88 | "- suppose now we added yet another function that we wish to cover under our generic implementation:\n", 89 | "\n", 90 | "```\n", 91 | "mul := n =>\n", 92 | " s := 1\n", 93 | " while n > 0:\n", 94 | " s := s * n\n", 95 | " n := n - 1\n", 96 | " return s\n", 97 | "```\n", 98 | "\n", 99 | "- unfortunately this function cannot be implemented in terms of `repeat`, because our last implementation only _adds_ the various values together, and `mul` needs to multiply them\n", 100 | "\n", 101 | "- in order to cover `mul` as well, we might generalize `repeat` even more, so that it also covers the operation that we want to perform between steps in order to merge `s` and `STEP(n)`:\n", 102 | "\n", 103 | "```\n", 104 | "sum := n =>\n", 105 | " s := BASE\n", 106 | " while n > 0:\n", 107 | " s := MERGE(s, STEP(n))\n", 108 | " n := n - 1\n", 109 | " return s\n", 110 | "\n", 111 | "line := n =>\n", 112 | " s := BASE\n", 113 | " while n > 0:\n", 114 | " s := MERGE(s, STEP(n))\n", 115 | " n := n - 1\n", 116 | " return s\n", 117 | "\n", 118 | "mul := n =>\n", 119 | " s := BASE\n", 120 | " while n > 0:\n", 121 | " s := MERGE(s, STEP(n))\n", 122 | " n := n - 1\n", 123 | " return s\n", 124 | "```\n", 125 | "\n", 126 | "- with this more powerful formulation, we could indeed build a very generic `repeat` function that merges `s` and `STEP(n)`, or we could refine it even more\n", 127 | " - we could perform `MERGE` and `STEP` together in a single step, and this new \"step which merges\" needs to know the current `s` and the step `n`\n", 128 | " - this results in\n", 129 | "\n", 130 | "```\n", 131 | "sum := n =>\n", 132 | " s := BASE\n", 133 | " while n > 0:\n", 134 | " s := MERGE_STEP(s, n)\n", 135 | " n := n - 1\n", 136 | " return s\n", 137 | "\n", 138 | "line := n =>\n", 139 | " s := BASE\n", 140 | " while n > 0:\n", 141 | " s := MERGE_STEP(s, n)\n", 142 | " n := n - 1\n", 143 | " return s\n", 144 | "\n", 145 | "mul := n =>\n", 146 | " s := BASE\n", 147 | " while n > 0:\n", 148 | " s := MERGE_STEP(s, n)\n", 149 | " n := n - 1\n", 150 | " return s\n", 151 | "```\n", 152 | "\n", 153 | "- this formulation offers a slight advantage: instead of three externally provided parameters, we only need two\n", 154 | "- this reduces the amount of context we need to provide\n", 155 | "\n", 156 | "- `repeat` therefore becomes:\n", 157 | "\n", 158 | "```\n", 159 | "repeat := (n,BASE,MERGE_STEP) =>\n", 160 | " s := BASE\n", 161 | " while n > 0:\n", 162 | " s := MERGE_STEP(s, n)\n", 163 | " n := n - 1\n", 164 | " return s\n", 165 | "```\n", 166 | "\n", 167 | "- and we can simply provide the proper parameters to `sum`, `line`, and `mul` as follows:\n", 168 | "\n", 169 | "```\n", 170 | "sum := n => return repeat(n, 0, (s,n) => return s + n)\n", 171 | "mul := n => return repeat(n, 1, (s,n) => return s * n)\n", 172 | "line := n => return repeat(n, \"\", (s,n) => return s + \"*\")\n", 173 | "```\n", 174 | "\n", 175 | "- we can take even a step further and further improve the definition of `repeat`, since it is clear that `n` is \"another sort\" of parameter\n", 176 | " - `n` remains a parameter even in the functions `sum`, `mul`, and `line`\n", 177 | "- we can turn `repeat` into a function that further returns a function (remember that functions are **just values**)\n", 178 | "\n", 179 | "```\n", 180 | "repeat := (BASE,MERGE_STEP) =>\n", 181 | " return n =>\n", 182 | " s := BASE\n", 183 | " while n > 0:\n", 184 | " s = MERGE_STEP(s, n)\n", 185 | " n = n - 1\n", 186 | " return s\n", 187 | "```\n", 188 | "\n", 189 | "- now we can specify `BASE` and `MERGE_STEP` as the first parameters, without being forced to provide a fake `n` which we just pass-through:\n", 190 | "\n", 191 | "```\n", 192 | "sum := return repeat(0, (s,n) => return s + n)\n", 193 | "mul := return repeat(1, (s,n) => return s * n)\n", 194 | "line := return repeat(\"\", (s,n) => return s + \"*\")\n", 195 | "```\n", 196 | "\n", 197 | "- as one last example, let us consider a very general HOF\n", 198 | " - perhaps the most general\n", 199 | "- `then`, to create function pipelines\n", 200 | " - the idea is that given two functions, we can invoke them in sequence in order to create a pipeline of them\n", 201 | " - this is a simple concept, but because of its simplicity and generality it can be used almost anywhere\n", 202 | "\n", 203 | "```\n", 204 | "then := (f,g) => return x => g(f(x))\n", 205 | "```\n", 206 | "\n", 207 | "- as an example, consider simple functions such as:\n", 208 | "\n", 209 | "```\n", 210 | "incr := x => return x + 1\n", 211 | "double := x => return x * 2\n", 212 | "```\n", 213 | "\n", 214 | "- suppose we wanted to increment twice\n", 215 | " - with `then`, we simply say\n", 216 | "```\n", 217 | "then(incr,incr)\n", 218 | "```\n", 219 | "\n", 220 | "- what about first incrementing, then doubling?\n", 221 | "\n", 222 | "```\n", 223 | "then(incr, double)\n", 224 | "```\n", 225 | "\n", 226 | "- or even further:\n", 227 | "\n", 228 | "```\n", 229 | "then(incr, then(double, incr))\n", 230 | "```\n", 231 | "\n", 232 | "- thanks to HOF's, we can create (composable) pipelines of functions, in order to assemble our custom instructions with what effectively becomes a \"custom semicolon instruction\"\n", 233 | " - our toolbox is not only growing, but our ability to compose concepts together is growing as well!\n", 234 | " - composition is of quintessential importance: without composition, our ability to connect concepts together is limited, and so is our ability to represent _networks of ideas_, which characterize most interesting and articulated problem domains" 235 | ] 236 | } 237 | ], 238 | "metadata": { 239 | "kernelspec": { 240 | "display_name": "Python 3", 241 | "language": "python", 242 | "name": "python3" 243 | }, 244 | "language_info": { 245 | "codemirror_mode": { 246 | "name": "ipython", 247 | "version": 3 248 | }, 249 | "file_extension": ".py", 250 | "mimetype": "text/x-python", 251 | "name": "python", 252 | "nbconvert_exporter": "python", 253 | "pygments_lexer": "ipython3", 254 | "version": "3.5.2" 255 | } 256 | }, 257 | "nbformat": 4, 258 | "nbformat_minor": 1 259 | } 260 | -------------------------------------------------------------------------------- /Lectures/Lecture7/practice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Activities" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Note: the practice happens almost exclusively on paper, potentially augmented by multiple choice questions in order to submit an answer that shows understanding.\n", 15 | "\n", 16 | "The activities performed are:\n", 17 | "\n", 18 | "- simple arithmetic operations with `repeat` (HOF variant):\n", 19 | " - sum\n", 20 | " - multiplication\n", 21 | " - power\n", 22 | "- complex drawing operations with `repeat` (HOF variant):\n", 23 | " - triangle (via rectangle trapezoid)\n", 24 | " - pyramid (via symmetric trapezoid)" 25 | ] 26 | } 27 | ], 28 | "metadata": { 29 | "kernelspec": { 30 | "display_name": "Python 3", 31 | "language": "python", 32 | "name": "python3" 33 | }, 34 | "language_info": { 35 | "codemirror_mode": { 36 | "name": "ipython", 37 | "version": 3 38 | }, 39 | "file_extension": ".py", 40 | "mimetype": "text/x-python", 41 | "name": "python", 42 | "nbconvert_exporter": "python", 43 | "pygments_lexer": "ipython3", 44 | "version": "3.5.2" 45 | } 46 | }, 47 | "nbformat": 4, 48 | "nbformat_minor": 1 49 | } 50 | -------------------------------------------------------------------------------- /Lectures/Lecture7/theory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Examples of higher order functions and partial application" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "_Note_: ensure that students copy, by hand and on paper, the various definitions written by the teacher on the whiteboard. It is strongly advised to ask students *not* to use a laptop, as it will prove distracting.\n", 15 | "\n", 16 | "The topics discussed are:\n", 17 | "\n", 18 | "- let us test the limits of the theory we have just given by building simple but concrete programs with it\n", 19 | "- we define some functions by only using the generic combinators `repeat` and `then`\n", 20 | " - as usual, let us begin with a very simple problem: drawing lines recursively" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 2, 26 | "metadata": {}, 27 | "outputs": [ 28 | { 29 | "name": "stdout", 30 | "output_type": "stream", 31 | "text": [ 32 | "3\n" 33 | ] 34 | }, 35 | { 36 | "data": { 37 | "text/plain": [ 38 | "'***'" 39 | ] 40 | }, 41 | "execution_count": 2, 42 | "metadata": {}, 43 | "output_type": "execute_result" 44 | } 45 | ], 46 | "source": [ 47 | "def line(n):\n", 48 | " if n <= 0:\n", 49 | " return \"\"\n", 50 | " else:\n", 51 | " return \"*\" + line(n-1)\n", 52 | "line(int(input()))" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "- if we observe the inductive case, we could read the expression `\"*\" + line(n-1)` as _draw an asterisk, then the rest of the line_\n", 60 | "- we might want to rewrite the code so that it more literally matches the description:\n", 61 | " - our target is the highly descriptive `then(asterisk, line(n-1))` (_prefix_ instead of _infix_, but it is the same)\n", 62 | "- let us reformulate the task of `line`\n", 63 | " - instead of just producing the line of asterisks, we want to **create a function that will add a line of asterisks to some initial string**\n", 64 | " - so `line` returns a function that, given a string, adds a line of asterisks, for example:\n", 65 | " - in general, drawing anything will become a function that adds the drawn element to a string which is passed as a parameter\n", 66 | " \n", 67 | "- according to our new formulation of drawing:\n", 68 | " - drawing nothing will simply add nothing to the string it receives as input\n", 69 | " - drawing an asterisk will simply add an asterisk to the string it receives as input\n", 70 | " - drawing a newline will simply add a newline to the string it receives as input \n", 71 | " - .." 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 24, 77 | "metadata": {}, 78 | "outputs": [ 79 | { 80 | "name": "stdout", 81 | "output_type": "stream", 82 | "text": [ 83 | "\n", 84 | "...\n", 85 | "*\n", 86 | "...*\n" 87 | ] 88 | } 89 | ], 90 | "source": [ 91 | "def nothing(s): return s\n", 92 | "def asterisk(s): return s + \"*\"\n", 93 | "def space(s): return s + \" \"\n", 94 | "def newline(s): return s + \"\\n\"\n", 95 | "\n", 96 | "print(nothing(\"\"))\n", 97 | "print(nothing(\"...\"))\n", 98 | "\n", 99 | "print(asterisk(\"\"))\n", 100 | "print(asterisk(\"...\"))" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "- suppose we wanted to draw more than an asterisk\n", 108 | "- we would simply call the asterisk function multiple times:" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 19, 114 | "metadata": {}, 115 | "outputs": [ 116 | { 117 | "name": "stdout", 118 | "output_type": "stream", 119 | "text": [ 120 | "**\n", 121 | "***\n" 122 | ] 123 | } 124 | ], 125 | "source": [ 126 | "print(asterisk(asterisk(\"\")))\n", 127 | "print(asterisk(asterisk(asterisk(\"\"))))" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "metadata": {}, 133 | "source": [ 134 | "- because of the many brackets, this is a bit cumbersome to read\n", 135 | "- if we observe the chain of calls to `asterisk` though, we could observe that we are simply calling `asterisk` on the output of another `asterisk` function (or more)\n", 136 | "- we could represent this explicitly by using the `then` function, which is exactly built to encapsulate this concept" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 25, 142 | "metadata": {}, 143 | "outputs": [ 144 | { 145 | "name": "stdout", 146 | "output_type": "stream", 147 | "text": [ 148 | "**\n", 149 | "***\n", 150 | "!!!***\n", 151 | "* *\n" 152 | ] 153 | } 154 | ], 155 | "source": [ 156 | "def then(f,g): \n", 157 | " return lambda x: g(f(x))\n", 158 | "\n", 159 | "print(then(asterisk, asterisk)(\"\"))\n", 160 | "print(then(asterisk, then(asterisk, asterisk))(\"\"))\n", 161 | "print(then(asterisk, then(asterisk, asterisk))(\"!!!\"))\n", 162 | "print(then(asterisk, then(space, asterisk))(\"\"))" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "- we are now building pipelines of drawing functions, but until we pass the starting string, nothing gets drawn\n", 170 | " - we are delaying drawing now\n", 171 | "- of course we could also notice that `nothing` can always be added to such a pipeline without changing its effect" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": 5, 177 | "metadata": {}, 178 | "outputs": [ 179 | { 180 | "name": "stdout", 181 | "output_type": "stream", 182 | "text": [ 183 | "*\n", 184 | "*\n" 185 | ] 186 | } 187 | ], 188 | "source": [ 189 | "print(then(nothing, asterisk)(\"\"))\n", 190 | "print(then(asterisk, nothing)(\"\"))" 191 | ] 192 | }, 193 | { 194 | "cell_type": "markdown", 195 | "metadata": {}, 196 | "source": [ 197 | "- at this point, we can easily (for a large enough value of \"easily\") assemble the `line` function\n", 198 | "- an empty line simply _adds nothing_, as there is nothing to add\n", 199 | "- a non-empty line first adds an asterisk, then the rest of the line" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": 6, 205 | "metadata": {}, 206 | "outputs": [ 207 | { 208 | "name": "stdout", 209 | "output_type": "stream", 210 | "text": [ 211 | "3\n" 212 | ] 213 | }, 214 | { 215 | "data": { 216 | "text/plain": [ 217 | "'***'" 218 | ] 219 | }, 220 | "execution_count": 6, 221 | "metadata": {}, 222 | "output_type": "execute_result" 223 | } 224 | ], 225 | "source": [ 226 | "def line(n):\n", 227 | " if n <= 0:\n", 228 | " return nothing\n", 229 | " else:\n", 230 | " return then(asterisk, line(n-1))\n", 231 | "\n", 232 | "line(int(input()))(\"\")" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": {}, 238 | "source": [ 239 | "- let us continue our journey\n", 240 | "- a rectangle with `n` rows and `m` columns is drawn as:\n", 241 | " - nothing if `n` is zero\n", 242 | " - a line, then a newline, and then a rectangle with n-1 rows otherwise\n", 243 | "- in code, this simply becomes:" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 8, 249 | "metadata": {}, 250 | "outputs": [ 251 | { 252 | "name": "stdout", 253 | "output_type": "stream", 254 | "text": [ 255 | "3\n", 256 | "***\n", 257 | "***\n", 258 | "***\n", 259 | "\n" 260 | ] 261 | } 262 | ], 263 | "source": [ 264 | "def rectangle(n,m):\n", 265 | " if n <= 0:\n", 266 | " return nothing\n", 267 | " else:\n", 268 | " return then(then(line(m), newline), rectangle(n-1,m))\n", 269 | " \n", 270 | "n = int(input())\n", 271 | "print(rectangle(n,n)(\"\"))" 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "metadata": {}, 277 | "source": [ 278 | "- of course the very same treatment can be applied to all functions we have seen so far\n", 279 | " - including general-looking functions such as `repeat`\n", 280 | " - we could reformulate `repeat` as a function which accepts a function as input, and then executes it `n` times starting from the same input\n", 281 | " - `repeat(f,n)` will thus yield $\\texttt{lambda x:} \\underbrace{\\texttt{f(...(f(x)))}}_{n \\text{ times}}$\n", 282 | "- we can implement `repeat` easily as a function which \n", 283 | " - returns `nothing` if `n` is zero, as there is nothing to do\n", 284 | " - first performs `f` once, `then` it `repeat`s `f` `n-1` times otherwise " 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": 18, 290 | "metadata": {}, 291 | "outputs": [ 292 | { 293 | "name": "stdout", 294 | "output_type": "stream", 295 | "text": [ 296 | "10\n", 297 | "1024\n" 298 | ] 299 | } 300 | ], 301 | "source": [ 302 | "def repeat(f,n):\n", 303 | " if n <= 0:\n", 304 | " return nothing\n", 305 | " else:\n", 306 | " return then(f, repeat(f, n-1))\n", 307 | "\n", 308 | "print(repeat(lambda x: x * 2, int(input()))(1))" 309 | ] 310 | }, 311 | { 312 | "cell_type": "markdown", 313 | "metadata": {}, 314 | "source": [ 315 | "- notice how now functions such as `line` recover their original structure based upon `repeat`, just at another (higher) level of abstraction" 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": 21, 321 | "metadata": {}, 322 | "outputs": [ 323 | { 324 | "name": "stdout", 325 | "output_type": "stream", 326 | "text": [ 327 | "5\n", 328 | "*****\n" 329 | ] 330 | } 331 | ], 332 | "source": [ 333 | "def line(n): return repeat(asterisk, n)\n", 334 | "\n", 335 | "print(line(int(input()))(\"\"))" 336 | ] 337 | }, 338 | { 339 | "cell_type": "markdown", 340 | "metadata": {}, 341 | "source": [ 342 | "- similarly, a square will also become the repetition of lines (each followed by a newline of course):" 343 | ] 344 | }, 345 | { 346 | "cell_type": "code", 347 | "execution_count": 23, 348 | "metadata": {}, 349 | "outputs": [ 350 | { 351 | "name": "stdout", 352 | "output_type": "stream", 353 | "text": [ 354 | "5\n", 355 | "*****\n", 356 | "*****\n", 357 | "*****\n", 358 | "*****\n", 359 | "*****\n", 360 | "\n" 361 | ] 362 | } 363 | ], 364 | "source": [ 365 | "def square(n): return repeat(then(line(n), newline), n)\n", 366 | "\n", 367 | "print(square(int(input()))(\"\"))" 368 | ] 369 | }, 370 | { 371 | "cell_type": "markdown", 372 | "metadata": {}, 373 | "source": [ 374 | "- hollow squares are built up from hollow lines, but for the rest follow the same approach:" 375 | ] 376 | }, 377 | { 378 | "cell_type": "code", 379 | "execution_count": 36, 380 | "metadata": {}, 381 | "outputs": [ 382 | { 383 | "name": "stdout", 384 | "output_type": "stream", 385 | "text": [ 386 | "4\n", 387 | "****\n", 388 | "* *\n", 389 | "* *\n", 390 | "****\n" 391 | ] 392 | } 393 | ], 394 | "source": [ 395 | "def hollow_line(n): return then(asterisk, then(repeat(space, n-2), asterisk))\n", 396 | "def hollow_square(n): return then(then(line(n), newline), then(repeat(then(hollow_line(n), newline), n-2), line(n)))\n", 397 | "\n", 398 | "print(hollow_square(int(input()))(\"\"))" 399 | ] 400 | } 401 | ], 402 | "metadata": { 403 | "kernelspec": { 404 | "display_name": "Python 3", 405 | "language": "python", 406 | "name": "python3" 407 | }, 408 | "language_info": { 409 | "codemirror_mode": { 410 | "name": "ipython", 411 | "version": 3 412 | }, 413 | "file_extension": ".py", 414 | "mimetype": "text/x-python", 415 | "name": "python", 416 | "nbconvert_exporter": "python", 417 | "pygments_lexer": "ipython3", 418 | "version": "3.5.2" 419 | } 420 | }, 421 | "nbformat": 4, 422 | "nbformat_minor": 1 423 | } 424 | -------------------------------------------------------------------------------- /Lectures/Lecture8/practice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Activities" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Note: the practice happens almost exclusively on paper, potentially augmented by multiple choice questions in order to submit an answer that shows understanding.\n", 15 | "\n", 16 | "The activities performed are:\n", 17 | "\n", 18 | "- drawing with combinators\n", 19 | "- to keep the assignments short, begin by combining predicates and invoking them directly, without drawing\n", 20 | " - `not`, `or`, `pic`, `flip_hor`, `flip_ver`, `transpose`, `scale`, `clip`\n", 21 | " - call each composed predicate a maximum of four times with different arguments\n", 22 | "- only one or two full drawing assignments" 23 | ] 24 | } 25 | ], 26 | "metadata": { 27 | "kernelspec": { 28 | "display_name": "Python 3", 29 | "language": "python", 30 | "name": "python3" 31 | }, 32 | "language_info": { 33 | "codemirror_mode": { 34 | "name": "ipython", 35 | "version": 3 36 | }, 37 | "file_extension": ".py", 38 | "mimetype": "text/x-python", 39 | "name": "python", 40 | "nbconvert_exporter": "python", 41 | "pygments_lexer": "ipython3", 42 | "version": "3.5.2" 43 | } 44 | }, 45 | "nbformat": 4, 46 | "nbformat_minor": 1 47 | } 48 | -------------------------------------------------------------------------------- /Lectures/Lecture8/samples.py: -------------------------------------------------------------------------------- 1 | n = 11 2 | y = 0 3 | 4 | # some combinators (not meant for lesson, only for teacher to draw similarities) 5 | Trans = lambda p: lambda x,y: p(y,x) 6 | FlipX = lambda p: lambda x,y: p(n - 1 - x,y) 7 | FlipY = lambda p: lambda x,y: p(x,n - 1 - y) 8 | Plus = lambda p,q: lambda x,y: p(x,y) or q(x,y) 9 | Times = lambda p,q: lambda x,y: p(x,y) and q(x,y) 10 | 11 | # square condition 12 | Square = lambda x,y: True 13 | 14 | # hollow square condition 15 | Left = lambda x,y: x == 0 16 | Top = Trans(Left) 17 | Right = lambda x,y: x == n - 1 18 | Bottom = Trans(Right) 19 | HollowSquare = Plus(Plus(Left,Top), Plus(Right,Bottom)) 20 | 21 | # triangle(s) 22 | Triangle = lambda x,y: x <= y 23 | OtherTriangle = FlipX(Triangle) 24 | 25 | # pyramid 26 | Pyramid = Times(Triangle, OtherTriangle) 27 | NaivePyramid = lambda x,y: x <= y and (n - 1 - x) <= y 28 | 29 | # circle 30 | Circle = lambda x,y: (x - n / 2) * (x - n / 2) + (y - n / 2) * (y - n / 2) <= n / 2 * n / 2 31 | 32 | P = Circle 33 | s = "" 34 | while y < n: 35 | x = 0 36 | while x < n: 37 | if P(x,y): 38 | s = s + "*" 39 | else: 40 | s = s + " " 41 | x = x + 1 42 | s = s + "\n" 43 | y = y + 1 44 | 45 | print(s) -------------------------------------------------------------------------------- /Lectures/Lecture8/theory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Algebra of drawing functions" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "_Note_: ensure that students copy, by hand and on paper, the various definitions written by the teacher on the whiteboard. It is strongly advised to ask students *not* to use a laptop, as it will prove distracting.\n", 15 | "\n", 16 | "The topics discussed are:\n", 17 | "\n", 18 | "- a drawing function as we have built so far in general takes `n*n` decisions of the form \"is the current pixel an asterisk?\"\n", 19 | "- we could generalise this in a HOF:" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 5, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "def draw(n,m,p):\n", 29 | " s = \"\"\n", 30 | " i = 0\n", 31 | " while i < n:\n", 32 | " j = 0\n", 33 | " while j < m:\n", 34 | " if p(n,m,i,j):\n", 35 | " s = s + \"*\"\n", 36 | " else:\n", 37 | " s = s + \" \"\n", 38 | " j = j + 1\n", 39 | " s = s + \"\\n\"\n", 40 | " i = i + 1\n", 41 | " return s" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "- this function accepts as arguments the size of the picture to draw, and a function `p`\n", 49 | " - by specifying different values of `p` (as lambda expressions if needed), we completely change the picture drawn\n", 50 | " - for example, if `p` always returns `True`, we simply fill the picture with asterisks\n", 51 | " - on the other hand, if `p` always returns `False`, we simply get a completely empty picture\n", 52 | " - when `j <= i` then we get a triangle\n", 53 | " - and so on..." 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 6, 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "name": "stdout", 63 | "output_type": "stream", 64 | "text": [ 65 | "***\n", 66 | "***\n", 67 | "***\n", 68 | "\n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | "\n", 73 | "* \n", 74 | "** \n", 75 | "***\n", 76 | "\n" 77 | ] 78 | } 79 | ], 80 | "source": [ 81 | "print(draw(3,3,lambda n,m,i,j: True))\n", 82 | "print(draw(3,3,lambda n,m,i,j: False))\n", 83 | "print(draw(3,3,lambda n,m,i,j: j <= i))" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "- interestingly enough, we could now simply state that a function from four integers (width, height, row index, column index) into a boolean (pixel on/off) **is** a picture\n", 91 | " - we can simply pass such a function to `draw` and just see it drawn\n", 92 | "- let us now consider a hollow square\n", 93 | "- let us identify the borders: `i == 0`, `j == 0`, `i == n-1`, or `j == n-1`" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 8, 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "name": "stdout", 103 | "output_type": "stream", 104 | "text": [ 105 | "***\n", 106 | " \n", 107 | " \n", 108 | "\n", 109 | "* \n", 110 | "* \n", 111 | "* \n", 112 | "\n", 113 | " \n", 114 | " \n", 115 | "***\n", 116 | "\n", 117 | " *\n", 118 | " *\n", 119 | " *\n", 120 | "\n" 121 | ] 122 | } 123 | ], 124 | "source": [ 125 | "print(draw(3,3,lambda n,m,i,j: i == 0))\n", 126 | "print(draw(3,3,lambda n,m,i,j: j == 0))\n", 127 | "print(draw(3,3,lambda n,m,i,j: i == n-1))\n", 128 | "print(draw(3,3,lambda n,m,i,j: j == m-1))" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": {}, 134 | "source": [ 135 | "- in the same spirit as the previous lectures, we always look for abstractions that capture our idea of composing concepts together\n", 136 | "- given multiple functions such as `lambda n,m,i,j: i == 0`, `lambda n,m,i,j: j == 0`, etc., how do we compose them into a single function which represents the picture drawn by, for example, overlaying the pictures?\n", 137 | "- we could do this explicitly (and thus quite verbosely), as a first approximation\n", 138 | " - notice that the hollow square draws pixels which belong to the upper row, lower row, left column, **or** right column\n", 139 | " - we can literally just invoke the various functions for the image parts and combine the results in a boolean `or`" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 10, 145 | "metadata": {}, 146 | "outputs": [ 147 | { 148 | "name": "stdout", 149 | "output_type": "stream", 150 | "text": [ 151 | "***\n", 152 | "* *\n", 153 | "***\n", 154 | "\n" 155 | ] 156 | } 157 | ], 158 | "source": [ 159 | "upper_line = lambda n,m,i,j: i == 0\n", 160 | "left_line = lambda n,m,i,j: j == 0\n", 161 | "lower_line = lambda n,m,i,j: i == n-1\n", 162 | "right_line = lambda n,m,i,j: j == m-1\n", 163 | "\n", 164 | "hollow_square = lambda n,m,i,j: upper_line(n,m,i,j) or lower_line(n,m,i,j) or left_line(n,m,i,j) or right_line(n,m,i,j)\n", 165 | "print(draw(3,3,hollow_square))" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "- suppose now that we wanted to just draw the upper and lower rows\n", 173 | "- we would have to define another function, and even figure out some reasonable name for it" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 11, 179 | "metadata": {}, 180 | "outputs": [ 181 | { 182 | "name": "stdout", 183 | "output_type": "stream", 184 | "text": [ 185 | "***\n", 186 | " \n", 187 | "***\n", 188 | "\n" 189 | ] 190 | } 191 | ], 192 | "source": [ 193 | "upper_and_lower_rows = lambda n,m,i,j: upper_line(n,m,i,j) or lower_line(n,m,i,j)\n", 194 | "print(draw(3,3,upper_and_lower_rows))" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": {}, 200 | "source": [ 201 | "- of course we could now notice an emerging pattern: composing a picture as an `or` of multiple pictures\n", 202 | "- we could therefore define this pattern explicitly as a higher order function which takes two pictures as parameters, invokes both of them, and combines the results with an `or`" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": 12, 208 | "metadata": {}, 209 | "outputs": [ 210 | { 211 | "name": "stdout", 212 | "output_type": "stream", 213 | "text": [ 214 | "***\n", 215 | " \n", 216 | "***\n", 217 | "\n" 218 | ] 219 | } 220 | ], 221 | "source": [ 222 | "def pic_or(p,q):\n", 223 | " return lambda n,m,i,j: p(n,m,i,j) or q(n,m,i,j)\n", 224 | "print(draw(3,3,pic_or(upper_line, lower_line)))" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "- thanks to `pic_or`, which we call a _combinator_ (since it combines arbitrary pictures together), we can quickly draw different pictures" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": 20, 237 | "metadata": {}, 238 | "outputs": [ 239 | { 240 | "name": "stdout", 241 | "output_type": "stream", 242 | "text": [ 243 | "***\n", 244 | "* \n", 245 | "* \n", 246 | "\n", 247 | "* *\n", 248 | "** **\n", 249 | "******\n", 250 | "******\n", 251 | "******\n", 252 | "******\n", 253 | "\n" 254 | ] 255 | } 256 | ], 257 | "source": [ 258 | "print(draw(3,3,pic_or(upper_line, left_line)))\n", 259 | "print(draw(6,6,pic_or(lambda n,m,i,j: n-i-1 <= j, lambda n,m,i,j: j <= i)))" 260 | ] 261 | }, 262 | { 263 | "cell_type": "markdown", 264 | "metadata": {}, 265 | "source": [ 266 | "- given that `pic_or` is itself a picture, we can pass its result to `pic_or` itself in order to go on nesting" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 22, 272 | "metadata": {}, 273 | "outputs": [ 274 | { 275 | "name": "stdout", 276 | "output_type": "stream", 277 | "text": [ 278 | "*****\n", 279 | "* *\n", 280 | "* *\n", 281 | "* *\n", 282 | "*****\n", 283 | "\n" 284 | ] 285 | } 286 | ], 287 | "source": [ 288 | "print(draw(5,5,pic_or(pic_or(upper_line, lower_line), pic_or(left_line, right_line))))" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "- of course there must be many other combinators possible\n", 296 | "- in order to discover these combinators, we could: \n", 297 | " - perform a long exploration based on trial and error, and look for new and interesting ways to combine pictures, or\n", 298 | " - we could notice that pictures return booleans, and as such given multiple pictures the ways we can combine their output per pixel define the ways we can combine the pictures themselves\n", 299 | " - booleans can be combined with `and`, `or`, and `not`\n", 300 | " - `and` of two pictures produces their intersection, that is only the pixels which are lit in both pictures\n", 301 | " - `not` inverts the picture: the lit pixels become unlit, and vice-versa" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": 24, 307 | "metadata": {}, 308 | "outputs": [ 309 | { 310 | "name": "stdout", 311 | "output_type": "stream", 312 | "text": [ 313 | "* \n", 314 | " \n", 315 | " \n", 316 | "\n", 317 | " **\n", 318 | " **\n", 319 | " **\n", 320 | "\n" 321 | ] 322 | } 323 | ], 324 | "source": [ 325 | "def pic_and(p,q):\n", 326 | " return lambda n,m,i,j: p(n,m,i,j) and q(n,m,i,j)\n", 327 | "print(draw(3,3,pic_and(upper_line, left_line)))\n", 328 | "\n", 329 | "def pic_not(p):\n", 330 | " return lambda n,m,i,j: not p(n,m,i,j)\n", 331 | "print(draw(3,3,pic_not(left_line)))" 332 | ] 333 | }, 334 | { 335 | "cell_type": "markdown", 336 | "metadata": {}, 337 | "source": [ 338 | "- we have performed transformations on the output of the picture functions\n", 339 | "- we can also perform transformations on the parameters\n", 340 | " - one such transformation is flipping, horizontally and vertically\n", 341 | " - for example, we could invoke the picture function with `n-1` instead of `0`, `n-2` instead of `1`, etc." 342 | ] 343 | }, 344 | { 345 | "cell_type": "code", 346 | "execution_count": 29, 347 | "metadata": {}, 348 | "outputs": [ 349 | { 350 | "name": "stdout", 351 | "output_type": "stream", 352 | "text": [ 353 | "***\n", 354 | "** \n", 355 | "* \n", 356 | "\n", 357 | "***\n", 358 | " **\n", 359 | " *\n", 360 | "\n", 361 | "* \n", 362 | "** \n", 363 | "***\n", 364 | "\n", 365 | " *\n", 366 | " **\n", 367 | "***\n", 368 | "\n" 369 | ] 370 | } 371 | ], 372 | "source": [ 373 | "def flip_hor(p):\n", 374 | " return lambda n,m,i,j: p(n,m,i,m-1-j)\n", 375 | "def flip_ver(p):\n", 376 | " return lambda n,m,i,j: p(n,m,n-1-i,j)\n", 377 | "upper_right_triangle = lambda n,m,i,j: i <= j\n", 378 | "upper_left_triangle = flip_hor(upper_right_triangle)\n", 379 | "lower_right_triangle = flip_ver(upper_right_triangle)\n", 380 | "lower_left_triangle = flip_ver(upper_left_triangle)\n", 381 | "print(draw(3,3,upper_left_triangle))\n", 382 | "print(draw(3,3,upper_right_triangle))\n", 383 | "print(draw(3,3,lower_left_triangle))\n", 384 | "print(draw(3,3,lower_right_triangle))" 385 | ] 386 | }, 387 | { 388 | "cell_type": "markdown", 389 | "metadata": {}, 390 | "source": [ 391 | "- we could also notice that a pyramid is no more than the intersection of the two lower triangles, but now other pyramids which would have been much harder to draw also become trivial" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": 35, 397 | "metadata": {}, 398 | "outputs": [ 399 | { 400 | "name": "stdout", 401 | "output_type": "stream", 402 | "text": [ 403 | " \n", 404 | " \n", 405 | " * \n", 406 | " *** \n", 407 | "*****\n", 408 | "\n", 409 | "* \n", 410 | "** \n", 411 | "*** \n", 412 | "** \n", 413 | "* \n", 414 | "\n", 415 | "* *\n", 416 | "** **\n", 417 | "*****\n", 418 | "** **\n", 419 | "* *\n", 420 | "\n" 421 | ] 422 | } 423 | ], 424 | "source": [ 425 | "upper_pyramid = pic_and(upper_left_triangle, upper_right_triangle)\n", 426 | "lower_pyramid = pic_and(lower_left_triangle, lower_right_triangle)\n", 427 | "left_pyramid = pic_and(lower_left_triangle, upper_left_triangle)\n", 428 | "right_pyramid = pic_and(lower_right_triangle, upper_right_triangle)\n", 429 | "print(draw(5,5,lower_pyramid))\n", 430 | "print(draw(5,5,left_pyramid))\n", 431 | "\n", 432 | "\n", 433 | "butterfly = pic_or(left_pyramid, right_pyramid)\n", 434 | "print(draw(5,5,butterfly))" 435 | ] 436 | }, 437 | { 438 | "cell_type": "markdown", 439 | "metadata": {}, 440 | "source": [ 441 | "- as a final touch, let us create a circle by using the well-known Pythagoras' formula" 442 | ] 443 | }, 444 | { 445 | "cell_type": "code", 446 | "execution_count": 39, 447 | "metadata": {}, 448 | "outputs": [ 449 | { 450 | "name": "stdout", 451 | "output_type": "stream", 452 | "text": [ 453 | " *** \n", 454 | "*****\n", 455 | "*****\n", 456 | "*****\n", 457 | " *** \n", 458 | "\n", 459 | " ***** \n", 460 | " ********* \n", 461 | " ********** \n", 462 | " ********** \n", 463 | " ********* \n", 464 | "********* \n", 465 | "******** \n", 466 | "******* \n", 467 | "******** \n", 468 | "********* \n", 469 | " ********* \n", 470 | " ********** \n", 471 | " ********** \n", 472 | " ********* \n", 473 | " ***** \n", 474 | "\n" 475 | ] 476 | } 477 | ], 478 | "source": [ 479 | "circle = lambda n,m,i,j: (i-n//2)*(i-n//2)+(j-n//2)*(j-n//2) <= n*n//4\n", 480 | "print(draw(5,5,circle))" 481 | ] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "metadata": {}, 486 | "source": [ 487 | "- let us conclude with a classic" 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": 45, 493 | "metadata": {}, 494 | "outputs": [ 495 | { 496 | "name": "stdout", 497 | "output_type": "stream", 498 | "text": [ 499 | " ***** \n", 500 | " ********* \n", 501 | " ********** \n", 502 | " ****** *** \n", 503 | " ********* \n", 504 | "********* \n", 505 | "******** \n", 506 | "******* \n", 507 | "******** \n", 508 | "********* \n", 509 | " ********* \n", 510 | " ********** \n", 511 | " ********** \n", 512 | " ********* \n", 513 | " ***** \n", 514 | "\n" 515 | ] 516 | } 517 | ], 518 | "source": [ 519 | "print(draw(15,15,pic_and(circle, pic_not(pic_or(lambda n,m,i,j: i == n//4 and j == m//2, right_pyramid)))))" 520 | ] 521 | } 522 | ], 523 | "metadata": { 524 | "kernelspec": { 525 | "display_name": "Python 3", 526 | "language": "python", 527 | "name": "python3" 528 | }, 529 | "language_info": { 530 | "codemirror_mode": { 531 | "name": "ipython", 532 | "version": 3 533 | }, 534 | "file_extension": ".py", 535 | "mimetype": "text/x-python", 536 | "name": "python", 537 | "nbconvert_exporter": "python", 538 | "pygments_lexer": "ipython3", 539 | "version": "3.5.2" 540 | } 541 | }, 542 | "nbformat": 4, 543 | "nbformat_minor": 2 544 | } 545 | -------------------------------------------------------------------------------- /Reader/images/rectangle_to_rectangles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hogeschool/Development-2/f1fd0f3153ee4794cd2b4113b3cf9933aeba0938/Reader/images/rectangle_to_rectangles.png -------------------------------------------------------------------------------- /Reader/images/rectangle_to_row_rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hogeschool/Development-2/f1fd0f3153ee4794cd2b4113b3cf9933aeba0938/Reader/images/rectangle_to_row_rectangle.png -------------------------------------------------------------------------------- /Reader/images/trapeze_to_trapezes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hogeschool/Development-2/f1fd0f3153ee4794cd2b4113b3cf9933aeba0938/Reader/images/trapeze_to_trapezes.png -------------------------------------------------------------------------------- /Reader/images/triangle_to_row_triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hogeschool/Development-2/f1fd0f3153ee4794cd2b4113b3cf9933aeba0938/Reader/images/triangle_to_row_triangle.png -------------------------------------------------------------------------------- /Reader/lecture0.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Introduction" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "In this second part of our exploration of programming constructs from basic concepts, we will focus almost exclusively on to the concept of abstraction.\n", 15 | "\n", 16 | "Humans have an impressive array of senses, through which we can acquire huge amounts of information from our environment. Unfortunately, our ability to process all of these signals in depth at the same time is somewhat limited. Try, for example, to hold a delicate balance of objects in your hands while following a complex conversation at the same time: at least one of the two activities is doomed to failure. Given this fundamental inability to process everything we possibly could at the same time, we have evolved a new skill: abstraction. Abstraction is the basic principle by which humans dynamically shift their focus, in order to be able to process only information that is relevant to the task at hand. For example, while chasing prey, we need not get distracted by pretty flowers along the way (even though for mating purposes those very same flowers would be far more relevant for a young hunter)!" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "## Code abstraction\n", 24 | "The basic unit of information when programming is _code_. Code is how we encode (complex) concepts in a language understood by the computer. The basic constructs that a programming language offers us are somewhat generic and limited. In order to focus our attention on some constructs rather than others, we will **give names** to blocks of code in order to recall the whole block of code with a simpler name. We had done this already, implicitly, by giving names to the result of expressions: variables are indeed just names for some expression stored in the state. This process of giving names to all sorts of things, in order to recall and retrieve them in a way that we perceive as _contextual_ and logical, is the basic activity of the process of abstraction.\n", 25 | "\n", 26 | "We will not just give names to blocks of code. For an abstraction to be defined properly, we want the freedom to use it whenever we want without strange interferences, which are (alas!) too easy to introduce in code. We will therefore define boundaries of interaction between our program and its abstractions, in the form of _arguments_ and _return values_, so that the pieces of code that we abstract away will be independent from the rest of the systems we build." 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## Expected results\n", 34 | "By defining ways to name and reuse code, our programs will acquire a new, pleasant dimension: instead of being verbose, with meaning hidden under mountains of code, they will become shorter and to the point. The names of the code blocks become, effectively, new high level instructions in our programs. Such new high level instructions reflect concepts of the domain of the problem, so that the solution is told \"almost like a story\", while using names and concepts taken from the problem domain itself." 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "## Transformation and composition\n", 42 | "After learning to give names to blocks of code in order to make our programs more idiomatic for the problem domain, we will start climbing the ladder of abstraction in order to define higher order concepts.\n", 43 | "\n", 44 | "We will show how to _compose_ functions together into pipelines which pass data through each stage in the pipeline, optionally performing some transformation. By means of composition we will achieve the ability to define whole programs as the intelligent composition of a series of solution steps. Each step encodes some property of the solution, and the composition combines and preserves the various properties in order to ensure that the whole pipeline encodes all properties needed to define the solution.\n", 45 | "\n", 46 | "Of course, along the way we will need to define what _property of a solution_ means, and what we expect when _combining_ and _preserving_ such properties.\n", 47 | "\n", 48 | "Programming by means of (property preserving) composition of partial solutions is the essence of _engineering_ when building software." 49 | ] 50 | } 51 | ], 52 | "metadata": { 53 | "kernelspec": { 54 | "display_name": "Python 3", 55 | "language": "python", 56 | "name": "python3" 57 | }, 58 | "language_info": { 59 | "codemirror_mode": { 60 | "name": "ipython", 61 | "version": 3 62 | }, 63 | "file_extension": ".py", 64 | "mimetype": "text/x-python", 65 | "name": "python", 66 | "nbconvert_exporter": "python", 67 | "pygments_lexer": "ipython3", 68 | "version": "3.5.2" 69 | } 70 | }, 71 | "nbformat": 4, 72 | "nbformat_minor": 2 73 | } 74 | -------------------------------------------------------------------------------- /Reader/lecture1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Naming code" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "In this chapter we will discuss how code can be _named_, in order to use it more easily by name instead of repeating the whole block of code over and over again.\n", 15 | "\n", 16 | "This makes our programs shorter, since we remove repetition, but it also makes them more to the point, since we can give useful and descriptive names to our blocks of code. Such names form a layer of documentation which is then useful in order for developers to understand the program. The computer does not really take an interest in the names we give to variables: such names are only relevant for the human reader.\n", 17 | "\n", 18 | "For the moment we will focus on the concepts and build everything in our own abstract language, Python will come back in the picture at a later moment." 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "## About naming things\n", 26 | "Suppose we were writing a simple program that begins with:\n", 27 | "\n", 28 | "```\n", 29 | "age := int(input())\n", 30 | "...\n", 31 | "```\n", 32 | "\n", 33 | "For the computer, this program is effectively exactly the same as a similar program in which the variable is renamed, such as:\n", 34 | "\n", 35 | "```\n", 36 | "x := int(input())\n", 37 | "...\n", 38 | "```\n", 39 | "\n", 40 | "or \n", 41 | "\n", 42 | "```\n", 43 | "paycheck := int(input())\n", 44 | "...\n", 45 | "```\n", 46 | "\n", 47 | "Of course, since variable names are words (`or_even_sentences`) in natural language, the moment we read that a variable is called _age_, then we automatically assume that it will have something to do with the age of some entity.\n", 48 | "\n", 49 | "Furthermore, suppose the initial program went on as follows:\n", 50 | "\n", 51 | "```\n", 52 | "age := int(input())\n", 53 | "region := input()\n", 54 | "print(\"Your wine is \" + str(age) + \" years old and comes from \" + region)\n", 55 | "...\n", 56 | "```\n", 57 | "\n", 58 | "then it would be immediately clear that the program works with wines, but if the program said:\n", 59 | "\n", 60 | "```\n", 61 | "age := int(input())\n", 62 | "region := input()\n", 63 | "print(\"Your car is \" + str(age) + \" years old and was built in \" + region)\n", 64 | "...\n", 65 | "```\n", 66 | "\n", 67 | "then we immediately get another image in our mind. Of course, we could also write a (nonsensical?) program such as:\n", 68 | "\n", 69 | "```\n", 70 | "age := input()\n", 71 | "region := int(input())\n", 72 | "print(\"Your wine is \" + str(region) + \" years old and comes from \" + age)\n", 73 | "...\n", 74 | "```\n", 75 | "\n", 76 | "The fact that the program is nonsensical here refers to the fact that the names of the variables do not reflect their meaning in human intuition and understanding. The program will work, and produce a reasonable result to the end user, but this result is obfuscated by a very poor naming convention. In a similarly absurd fashion, we could choose to name all of our variables `v1`, `v2`, etc., resulting in poorly understandable programs such as:\n", 77 | "\n", 78 | "```\n", 79 | "v1 := int(input())\n", 80 | "v2 := input()\n", 81 | "print(\"Your wine is \" + str(v1) + \" years old and comes from \" + v2)\n", 82 | "...\n", 83 | "```\n", 84 | "\n", 85 | "This program is also obscure to understand, and it is easy to imagine that at a larger scale (inside a much longer program) this practice would make for impenetrable programs that a reader can hardly understand at a glance." 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "## Giving names to code\n", 93 | "For the reasons given above, the ability to give names is fundamental, and gives rise to what we call a (primitive) _taxonomy_. A taxonomy is an ordering of something into related concepts which all get a label, or name. A program does, indeed, label concepts with names (variables) and the expressions written in the code tie these concepts together making their relatioships explicit. By giving the _right_ names, we define the concepts that will be useful when building the solution to a problem in a way that makes them also _recognizable_ at a glance.\n", 94 | "\n", 95 | "So far, the only concepts that earned the right to a name were _values_, meaning we could define a hollow square as full lines and hollow lines stored in variables and then later composed together in the final picture:\n", 96 | "\n", 97 | "```\n", 98 | "n := int(input())\n", 99 | "full_line := \"\"\n", 100 | "i := n\n", 101 | "while i > 0 do full_line := full_line + \"*\"; i := i - 1\n", 102 | "hollow_line := \"*\"\n", 103 | "i := n-2\n", 104 | "while i > 0 do hollow_line := hollow_line + \" \"; i := i - 1\n", 105 | "hollow_line := hollow_line + \"*\"\n", 106 | "\n", 107 | "s := full_line + \"\\n\"\n", 108 | "while n > 2\n", 109 | " s := s + hollow_line + \"\\n\"\n", 110 | " n := n - 1\n", 111 | "s := s + full_line + \"\\n\"\n", 112 | "```\n", 113 | "\n", 114 | "Notice that by virtue of the names given to the line variables, we can encode _concepts_ that are not enough to solve the problem by themselves, but which are _a necessary part_ of the solution.\n", 115 | "\n", 116 | "Of course, we could say that a full line is a string, but what about _drawing a full line_? A string is _a thing_ in our taxonomy, and not enough to represent the concept of _performing an action_. Performing _actions_ is the quintessential domain of code, in that code is dynamic (_actions_) whereas state is static (_things_).\n", 117 | "\n", 118 | "Still, we feel a bit limited by our ability to only give names to _things_, because a taxonomy might very well include concepts such as \"turn left\", \"draw an asterisk\", \"draw a full line of length `n`\", and so on, which are perfectly valid and name-worthy.\n", 119 | "\n", 120 | "Giving names is usually done by binding a name to a value in the state. In order to give names to code, we want to be able to store, inside the state, blocks of code in addition to the other values (numbers, booleans, etc.). Such \"code values\" are called _lambda expressions_. The name, which sounds cospicuously impenetrable (what is a _lambda_ after all and what does it have to do with code?), goes back to the tradition of the very first programming languages. In the 1930's, Alonzo Church defined the first programming language, the _lambda calculus_, in which the definition of reusable code blocks was already present under the name \"lambda expression\" or \"lambda term\". For this reason, the name has remained unchanged over the decades, and is still used nowadays. We will see the word \"lambda\" even in Python!\n", 121 | "\n", 122 | "A lambda expression will support the basic form `<() => L>`, where `L` is a statement called _body_. Notice that code is, as always, surrounded by angle brackets `<` and `>`. Whenever we pass code blocks around, just like we did for our $\\text{eval}$ function, we will surround them by these brackets. We say that code inside such brackets is \"quoted\", or a \"code quotation\".\n", 123 | "\n", 124 | "We therefore extend our state so that it can also contain lambda expressions. This could give rise to states containing lambda expressions, such as for example:\n", 125 | "\n", 126 | "$$\\{ x := 0, \\text{incr} := \\text{< () } => x := x + 1> \\}$$\n", 127 | "\n", 128 | "In order to be able to bind a lambda expression to a name in the state, then we must support expressions of this kind in our programming language. Just like we could write `x := 0`, we want to be able to write `incr := <() => x := x + 1>`. For example, we could write a program such as:\n", 129 | "\n", 130 | "```\n", 131 | "x := 0\n", 132 | "incr := <() => x := x + 1>\n", 133 | "```\n", 134 | "\n", 135 | "which simply produces the state seen above. The combination of the name (`incr`) and its associated lambda expression (`<() => x := x + 1>`) are, together, called _a function_.\n", 136 | "\n", 137 | "Expressions such as `x + y` inside a lambda expression will implicitly replace `x` and `y` with whatever value they are bound to in the state. Expressions as we have seen so far are \"consumers\", or \"users\", of bindings, in the sense that they make use of the values stored in the state by substituting names for values.\n", 138 | "\n", 139 | "In order to make use of lambda expressions in the state just like we can use a number in the state, we need to introduce a new sort of expression or statement which will be able to perform the lookup of the lambda expression from the state and then do something meaningful with it. This instruction is called _invocation_ and has a very simple syntax: `V()`, where `V` is the name of the variable bound to a lambda. The semantics of invocation are quite simple: they just replace the invocation statement with the code found in the state:\n", 140 | "\n", 141 | "$\\text{eval}(<V()>, S) \\rightarrow <L>, S$ where $S[V] \\rightarrow <() \\Rightarrow L>$\n", 142 | "\n", 143 | "Let us see an example, step-by-step, of this new semantic rule in action:\n", 144 | "\n", 145 | "```\n", 146 | "x := 0\n", 147 | "y := 0\n", 148 | "incr_x := <() => x := x + 1>\n", 149 | "incr_y_by_x := <() => y := y + x>\n", 150 | "\n", 151 | "incr_x()\n", 152 | "incr_x()\n", 153 | "incr_y_by_x()\n", 154 | "done\n", 155 | "```\n", 156 | "\n", 157 | "|Program|State|\n", 158 | "|:-------:|:-----:|\n", 159 | "| `< x := 0; …>` | $\\{ {} \\}$ |\n", 160 | "| `< y:=0; …>` | $\\{ {x:=0} \\}$ |\n", 161 | "| `< incr_x := <() => x := x + 1>; …>` | $\\{ {x:=0, y:=0} \\}$ |\n", 162 | "| `< incr_y_by_x := <() => y := y + x>; …>` | $\\{ {x:=0, y:=0, incr\\_x := <() => x := x + 1>} \\}$ |\n", 163 | "| `< incr_x(); …>` | $\\{ {x:=0, y:=0, incr\\_x := <() => x := x + 1>, incr\\_y\\_by\\_x := <() => y := y + x>} \\}$ |\n", 164 | "| `< x := x + 1; …>` | $ … $ |\n", 165 | "| `< x := 0 + 1; …>` | $ … $ |\n", 166 | "| `< x := 1; …>` | $ … $ |\n", 167 | "| `< incr_x(); …>` | $\\{ {x:=1, y:=0, incr\\_x := <() => x := x + 1>, incr\\_y\\_by\\_x := <() => y := y + x>} \\}$ |\n", 168 | "| `< x := x + 1; …>` | $ … $ |\n", 169 | "| `< x := 1 + 1; …>` | $ … $ |\n", 170 | "| `< x := 2; …>` | $ … $ |\n", 171 | "| `< incr_y_by_x(); …>` | $\\{ {x:=2, y:=0, incr\\_x := <() => x := x + 1>, incr\\_y\\_by\\_x := <() => y := y + x>} \\}$ |\n", 172 | "| `< y := y + x; …>` | $ … $ |\n", 173 | "| `< y := 0 + x; …>` | $ … $ |\n", 174 | "| `< y := 0 + 2; …>` | $ … $ |\n", 175 | "| `< y := 2; …>` | $ … $ |\n", 176 | "| `< done >` | $\\{ {x:=2, y:=2, incr\\_x := <() => x := x + 1>, incr\\_y\\_by\\_x := <() => y := y + x>} \\}$ |\n", 177 | "\n", 178 | "Notice that the last three lines of the program are very readable and human-friendly. We can quickly see that we will increment `x` twice, and then we will increment `y` by `x`. This is made possible by the combination of functions and reasonable names. Thanks to the given names, the reader is gently guided along the same train of thoughts that led the original programmer to the current solution.\n", 179 | "\n", 180 | "Moreover, by invoking `incr_x` twice we avoid repeating code. Suppose we wanted to increment `x` twice, but now instead of by one by three. We only need to change the lambda expression assigned to `incr_x` to `<() => x := x + 3>`, and all of the invocations will be automatically adjusted accordingly. If we had duplicated the code `x := x + 1`, we would need to adjust this code in multiple places: this manual search-and-replace is tedious and error prone. Many bugs have been introduced by using this weak refactoring technique." 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": {}, 186 | "source": [ 187 | "## Climbing the abstraction ladder\n", 188 | "Let us go back for a moment to drawing square, as we have done in previous lectures. We want to define the solution to our problem in a way that clearly highlights how drawing a square is actually drawing a line multiple times. Since \"drawing a line\" is an action, it makes sense that it is encoded as a block of code, that is a function in our program.\n", 189 | "\n", 190 | "We could redefine our example as follows:\n", 191 | "\n", 192 | "```\n", 193 | "draw_line := <\n", 194 | " () =>\n", 195 | " x := n\n", 196 | " while x > 0 do\n", 197 | " s := s + \"*\"\n", 198 | " x := x - 1\n", 199 | " s := s + \"\\n\"\n", 200 | " >\n", 201 | "\n", 202 | "n := int(input())\n", 203 | "s := \"\"\n", 204 | "y := n\n", 205 | "while y > 0\n", 206 | " draw_line()\n", 207 | " y := y - 1\n", 208 | "```\n", 209 | "\n", 210 | "If we look at the last five lines of code, we could imagine that those are the actual program. The previous lines (the definition of `draw_line` as a function) are simply a sort of prelude that we can read once, and then trust and ignore. This means that we have removed \"drawing a line\" from our list of problems to solve, and now armed with the knowledge of how to draw such a line we only focus on drawing the whole square. Abstraction means, indeed, removal: when we climb the abstraction ladder, then we make our program (again, in our case this would be the last five lines) shorter and less detailed. Indeed, invoking `draw_line` is far more succinct and direct than the code for drawing the line itself!\n", 211 | "\n", 212 | "Abstraction is not a boolean property, in that a single step never yields the maximum possible abstraction and clarity. This means that we can keep going, and abstracting details away at each step in order to achieve _layers of abstraction_ that each build upon each other. We know how to draw a single line, then let us define drawing `n` lines, and voilá: we have introduced yet another useful layer:\n", 213 | "\n", 214 | "```\n", 215 | "draw_line := <\n", 216 | " () =>\n", 217 | " x := n\n", 218 | " while x > 0 do\n", 219 | " s := s + \"*\"\n", 220 | " x := x - 1\n", 221 | " s := s + \"\\n\"\n", 222 | " >\n", 223 | "\n", 224 | "draw_N_lines := <\n", 225 | " () =>\n", 226 | " y := n\n", 227 | " while y > 0\n", 228 | " draw_line()\n", 229 | " y := y - 1\n", 230 | " >\n", 231 | "\n", 232 | "n := int(input())\n", 233 | "s := \"\"\n", 234 | "draw_N_lines()\n", 235 | "```\n", 236 | "\n", 237 | "In this new formulation, the actual program has become three lines, and it just states that drawing a square is the same as \"drawing N lines\" (of asterisks).\n", 238 | "\n", 239 | "We can of course climb back from abstractions (our functions) down into their concrete details, just by finding their definition and reading their code." 240 | ] 241 | }, 242 | { 243 | "cell_type": "markdown", 244 | "metadata": {}, 245 | "source": [ 246 | "## External context and function parameters\n", 247 | "Lambda expressions as we have seen so far have a limitation: they encapsulate a code block (and therefore the behaviour that this code block represents), but have no way to receive some contextual information that further specifies what they must do. For example, let us consider `<() => x := x + 1>`: it clearly increments `x` by $1$. We might also want to define a lambda expression for incrementing the same variable by another number, say $2$: `<() => x := x + 2>`, or $3$: `<() => x := x + 3>`. Clearly all these functions do roughly the same (they all increment `x` by something), and so we would like to group them in a _family of functions_ that all increment `x`, but which are distinguished from each other by _how much_ they increment.\n", 248 | "\n", 249 | "This distinguishing of functions inside the family is done by means of an extra bit of information that we provide to the function when calling it. Such information is called a **parameter**. In the case of our example, we would write `<(k) => x := x + k>`, where `k` is a parameter that we **pass** to the function when invoking it. Invocation will therefore require us to specify the value of the parameter, for example by saying `incr_x(5)`. This value (in our case $5$) is called the **argument**.\n", 250 | "\n", 251 | "In general, we extend our function definition syntax so that it includes parameters: `<(Pars) => L>`, where `L` is a statement and `Pars` is a series of variable names separated by commas.\n", 252 | "\n", 253 | "Invocation must now supply arguments to the function, the arguments being a series of expressions such as `3`, `x + 1`, etc. The arguments are assigned to the parameters right before the function body is injected in the main program. This leads us to the following semantics:\n", 254 | "\n", 255 | "$\\text{eval}(<V(a_1, a_2, \\dots)>, S) \\rightarrow <p_1 := a_1; p_2 := a_2; \\dots; L>, S$ where $S[V] \\rightarrow <(p_1, p_2, \\dots) \\Rightarrow L>$ and $a_1, a_2, \\dots$ are all constant values.\n", 256 | "\n", 257 | "Note that we cannot directly assign the arguments $a_1, a_2, ...$ to the parameters $p_1, p_2, ...$ inside the state, because the arguments could be expressions (for example, 3 + 1) instead of atomic values, so we need to execute the code to evaluate it. This means that, before the rule above can become active, we need to check if some of the arguments are not (yet) constant, in a left-to-right fashion:\n", 258 | "\n", 259 | "$\\text{eval}(<V(\\dots, E_i, \\dots)>, S) \\rightarrow <\\dots, E_i', \\dots; L>, S$ where $\\text{eval}(<E_i>,S) \\rightarrow <E_i'>,S'$ and $E_i$ is the first argument from the left which is not a constant.\n", 260 | "\n", 261 | "As an example, consider the evaluation steps of the following program:\n", 262 | "\n", 263 | "```\n", 264 | "incr_x_by_k := <(k) => x := x + k>\n", 265 | "x := 0\n", 266 | "incr_x_by_k(1)\n", 267 | "incr_x_by_k(2)\n", 268 | "incr_x_by_k(x)\n", 269 | "done\n", 270 | "```\n", 271 | "|Program|State|\n", 272 | "|:-------:|:-----:|\n", 273 | "| `< incr_x_by_k := <(k) => x := x + k>; …>` | $\\{ {} \\}$ |\n", 274 | "| `< x := 0; …>` | $\\{ { incr\\_x\\_by\\_k := <(k) => x := x + k> } \\}$ |\n", 275 | "| `< incr_x_by_k(1); …>` | $\\{ { incr\\_x\\_by\\_k := <(k) => x := x + k>, x:=0 } \\}$ |\n", 276 | "| `< k:=1; x := x + k; …>` | $ … $ |\n", 277 | "| `< x := x + k; …>` | $\\{ { incr\\_x\\_by\\_k := <(k) => x := x + k>, x:=0, k:=1 } \\}$ |\n", 278 | "| `< x := 0 + k; …>` | $ … $ |\n", 279 | "| `< x := 0 + 1; …>` | $ … $ |\n", 280 | "| `< x := 1; …>` | $ … $ |\n", 281 | "| `< incr_x_by_k(2); …>` | $\\{ { incr\\_x\\_by\\_k := <(k) => x := x + k>, x:=1, k:=1 } \\}$ |\n", 282 | "| `< k:=2; x := x + k; …>` | $ … $ |\n", 283 | "| `< x := x + k; …>` | $\\{ { incr\\_x\\_by\\_k := <(k) => x := x + k>, x:=1, k:=2 } \\}$ |\n", 284 | "| `< x := 1 + k; …>` | $ … $ |\n", 285 | "| `< x := 1 + 2; …>` | $ … $ |\n", 286 | "| `< x := 3; …>` | $ … $ |\n", 287 | "| `< incr_x_by_k(x); …>` | $\\{ { incr\\_x\\_by\\_k := <(k) => x := x + k>, x:=3, k:=2 } \\}$ |\n", 288 | "| `< incr_x_by_k(3); …>` | $ … $ |\n", 289 | "| `< k:=3; x := x + k; …>` | $ … $ |\n", 290 | "| `< x := x + k; …>` | $\\{ { incr\\_x\\_by\\_k := <(k) => x := x + k>, x:=3, k:=3 } \\}$ |\n", 291 | "| `< x := 3 + k; …>` | $ … $ |\n", 292 | "| `< x := 3 + 3; …>` | $ … $ |\n", 293 | "| `< x := 6; …>` | $ … $ |\n", 294 | "| `< done >` | $\\{ { incr\\_x\\_by\\_k := <(k) => x := x + k>, x:=6, k:=3 } \\}$ |\n", 295 | "\n", 296 | "Notice that at each invocation we _reassign_ `k` to the value between the brackets, that is `k` is only useful during a single evaluation of the body of the function, and after that it should be \"forgotten\", as it just contains some meaningless trace of the last invocation of the function. We will explore this concept further in the following materials.\n", 297 | "\n", 298 | "We can also define multiple parameters of a lambda expression, in order to provide a richer context. For example, suppose we wanted to define a function that both increments and multiplies a variable. Then we would need this function to accept two parameters: the offset (to increment by) and the factor (to multiply by):\n", 299 | "\n", 300 | "```\n", 301 | "mul_incr := <(c,k) => x := x * c + k>\n", 302 | "x := 1\n", 303 | "mul_incr(5, 3)\n", 304 | "mul_incr(2, 1)\n", 305 | "done\n", 306 | "```\n", 307 | "|Program|State|\n", 308 | "|:-------:|:-----:|\n", 309 | "| `< mul_incr := <(c,k) => x := x * c + k>; …>` | $\\{ {} \\}$ |\n", 310 | "| `< x := 1; …>` | $\\{ {mul\\_incr := <(c,k) => x := x * c + k>} \\}$ |\n", 311 | "| `< mul_incr(5, 3); …>` | $\\{ {mul\\_incr := <(c,k) => x := x * c + k>, x:=1} \\}$ |\n", 312 | "| `< c:=5; k:=3; x := x * c + k; …>` | $ … $ |\n", 313 | "| `< k:=3; x := x * c + k; …>` | $\\{ {mul\\_incr := <(c,k) => x := x * c + k>, x:=1, c:=5} \\}$ |\n", 314 | "| `< x := x * c + k; …>` | $\\{ {mul\\_incr := <(c,k) => x := x * c + k>, x:=1, c:=5, k:=3} \\}$ |\n", 315 | "| `< x := 1 * c + k; …>` | $ … $ |\n", 316 | "| `< x := 1 * 5 + k; …>` | $ … $ |\n", 317 | "| `< x := 5 + k; …>` | $ … $ |\n", 318 | "| `< x := 5 + 3; …>` | $ … $ |\n", 319 | "| `< x := 8; …>` | $ … $ |\n", 320 | "| `< mul_incr(2, 1); …>` | $\\{ {mul\\_incr := <(c,k) => x := x * c + k>, x:=8, c:=5, k:=3} \\}$ |\n", 321 | "| `< c:=2; k:=1; x := x * c + k;…>` | $ … $ |\n", 322 | "| `< k:=1; x := x * c + k;…>` | $\\{ {mul\\_incr := <(c,k) => x := x * c + k>, x:=8, c:=2, k:=3} \\}$ |\n", 323 | "| `< x := x * c + k;…>` | $\\{ {mul\\_incr := <(c,k) => x := x * c + k>, x:=8, c:=2, k:=1} \\}$ |\n", 324 | "| `< x := 8 * c + k;…>` | $ … $ |\n", 325 | "| `< x := 8 * 2 + k;…>` | $ … $ |\n", 326 | "| `< x := 16 + k;…>` | $ … $ |\n", 327 | "| `< x := 16 + 1;…>` | $ … $ |\n", 328 | "| `< x := 17;…>` | $ … $ |\n", 329 | "| `< done >` | $\\{ {mul\\_incr := <(c,k) => x := x * c + k>, x:=17, c:=2, k:=1} \\}$ |\n" 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": null, 335 | "metadata": { 336 | "collapsed": true 337 | }, 338 | "outputs": [], 339 | "source": [] 340 | } 341 | ], 342 | "metadata": { 343 | "kernelspec": { 344 | "display_name": "Python 3", 345 | "language": "python", 346 | "name": "python3" 347 | }, 348 | "language_info": { 349 | "codemirror_mode": { 350 | "name": "ipython", 351 | "version": 3 352 | }, 353 | "file_extension": ".py", 354 | "mimetype": "text/x-python", 355 | "name": "python", 356 | "nbconvert_exporter": "python", 357 | "pygments_lexer": "ipython3", 358 | "version": "3.6.3" 359 | } 360 | }, 361 | "nbformat": 4, 362 | "nbformat_minor": 1 363 | } 364 | -------------------------------------------------------------------------------- /Reader/lecture5.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Designing recursive functions" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Now that we have seen how recursion works, we will focus on how to design recursive functions starting from an analysis of a problem. We will then translate the recursive functions in Python.\n", 15 | "\n", 16 | "## General strategy\n", 17 | "When confronted with a difficult problem, it is always helpful to draw it. The ability to draw something out is not just useful for aesthetic reasons: drawing the problem (with precision!) forces us to reckon with all the details of the problem, and to visualise them so we can properly deal with them.\n", 18 | "\n", 19 | "After drawing or plotting the problem, we look for _regularities_. Regularities are powerful elements in a solution, because they suggest the building blocks of both the problem and therefore the code that will become the solution. Such regularities present themselves as smaller instances of the problem itself, which we also call _recurring structure_ or _inductive case_. Without regularities (or without seeing them) it is very hard to produce a solution.\n", 20 | "\n", 21 | "After we have found the recurring structure, we must find what ties them together. For example, even though it is true that a line is made up of smaller lines, we must also define what \"made up of\" really means: in this case, concatenation.\n", 22 | "\n", 23 | "The recurring structure should recur in a specific way: every time, at a smaller scale. The line must be made up of _smaller_ lines, and at some point we expect a line so small that we do not really need to split it further. This definition of when do we stop splitting our problem into smaller versions of itself is called the _base case_." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "## Playing with numbers\n", 31 | "\n", 32 | "As a first example, let us consider the problem of computing the sum of all numbers in the range $0 \\dots n$ for a given $n$. The recurring structure can be seen just by putting brackets to identify that a sum is actually a smaller sum, plus the last number:\n", 33 | "\n", 34 | "$$(0 + 1 + 2 + \\dots + n-1 + n) = ((((0 + 1) + 2)\\dots) + n-1) + n$$\n", 35 | "\n", 36 | "The brackets explicitly denote a sub-problem. Of course, we could rephrase this in a way that makes it even more evident where the recurring structure occurs. Whenever we have a series of numbers, such as $0 + \\dots + n$ added together and within brackets, we will write $\\text{sum}(0,n)$:\n", 37 | "\n", 38 | "\\begin{align*}\n", 39 | "\\text{sum}(0,n) \n", 40 | "&= \\text{sum}(0,n-1) + n \\\\\n", 41 | "&= (\\text{sum}(0,n-2) + n-1) + n \\\\\n", 42 | "&= ((\\text{sum}(0,n-3) + n-2) + n-1) + n \\\\\n", 43 | "&= \\vdots \\\\\n", 44 | "&= (((0 + 1 \\dots) + n-2) + n-1) + n \\\\\n", 45 | "\\end{align*}\n", 46 | "\n", 47 | "This formulation should also make it evident how the base case will be zero: adding all numbers up to zero will simply result in zero itself.\n", 48 | "\n", 49 | "We can translate this into a recursive function as follows:" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 6, 55 | "metadata": {}, 56 | "outputs": [ 57 | { 58 | "name": "stdout", 59 | "output_type": "stream", 60 | "text": [ 61 | "6\n", 62 | "21\n" 63 | ] 64 | } 65 | ], 66 | "source": [ 67 | "def sum(n):\n", 68 | " if n <= 0: return 0\n", 69 | " else: return sum(n-1) + n\n", 70 | "print(sum(int(input())))" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "We could of course also look for other recurring structures. For example, we could notice that the sum of numbers up until $n$ is actually the sum of all numbers within the range $0 \\dots n$. If we focus on the sum over a range, then more decomposition possibilities emerge. One of these would, for example, split the range in two half ranges:\n", 78 | "\n", 79 | "$$0 + 1 + \\dots + n = (0 + 1 + \\dots + n//2) + ((n//2+1) + \\dots + n)$$\n", 80 | "\n", 81 | "For example, the sum $0 + 1 + 2 + 3 + 4 + 5$ could be reformulated as the sum of $0 + 1 + 2$ and $3 + 4 + 5$. The sub-sequences respectively end at the middle point of the previous subsequence, $2 = 5//2$, and start at its successor $3 = (5//2 + 1)$.\n", 82 | "\n", 83 | "Until now, the only variable in this formulation was the last number to add, $n$. To add an extra step of complexity in this formulation, we could also vary the beginning of the range, which until now was $0$. We will denote the beginning of the sequence with $l$ (_lower_ bound) and the end of the sequence with $u$ (_upper_ bound).\n", 84 | "\n", 85 | "When adding (sub-) sequences, we actually have two base cases: the empty sequence, when $l > u$ (it is empty because there can be no values between numbers where the first is greater than the second), adds to $0$; the sequence with only one element, when $l = u$, adds to $l$.\n", 86 | "\n", 87 | "\\begin{align*}\n", 88 | "\\text{sum_range}(l,u) &= 0 \\text{ when } l > u \\\\\n", 89 | "&= l \\text{ when } l = u \\\\\n", 90 | "&= \\text{sum_range}(l,m) + \\text{sum_range}(m+1,u) \\text{ otherwise, where } m = (l+u)//2 \\\\\n", 91 | "\\end{align*}\n", 92 | "\n", 93 | "Turned into code, this becomes:" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 15, 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "name": "stdout", 103 | "output_type": "stream", 104 | "text": [ 105 | "7\n", 106 | "28\n" 107 | ] 108 | } 109 | ], 110 | "source": [ 111 | "def sum_range(l, u):\n", 112 | " if l > u:\n", 113 | " return 0\n", 114 | " elif l == u:\n", 115 | " return l\n", 116 | " else:\n", 117 | " middle_point = (l + u) // 2\n", 118 | " fst_half = sum_range(l, middle_point)\n", 119 | " snd_half = sum_range(middle_point + 1, u)\n", 120 | " return fst_half + snd_half\n", 121 | "\n", 122 | "def sum(n):\n", 123 | " return sum_range(1, n)\n", 124 | "\n", 125 | "print(sum(int(input())))" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "### More examples with numbers\n", 133 | "\n", 134 | "We can perform a very similar operation to adding a range of numbers: multiplying a range. For example, the product of numbers up until $3$ would be $1 \\times 2 \\times 3$.\n", 135 | "\n", 136 | "Just like we did for the sum, we can decompose the product into the product of a shorter range, multiplied by the last number. Thus, the product of an empty sequence of numbers is $\\text{prod}(0) = 1$ (should we choose $0$, then we would always get $0$ also for non-empty sequences!), whereas the product of a non-empty sequence of numbers up to $n$ is the product of the sequence up to $n-1$, multiplied by $n$: $\\text{prod}(n) = \\text{prod}(n-1) \\times n$.\n", 137 | "\n", 138 | "This becomes the simple recursive function:" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 23, 144 | "metadata": {}, 145 | "outputs": [ 146 | { 147 | "name": "stdout", 148 | "output_type": "stream", 149 | "text": [ 150 | "4\n", 151 | "24\n" 152 | ] 153 | } 154 | ], 155 | "source": [ 156 | "def prod(n):\n", 157 | " if n <= 0:\n", 158 | " return 1\n", 159 | " else:\n", 160 | " return prod(n-1) * n\n", 161 | "print(prod(int(input())))" 162 | ] 163 | }, 164 | { 165 | "cell_type": "markdown", 166 | "metadata": {}, 167 | "source": [ 168 | "With the very same trick, we could define the power $b^a = \\underbrace{b \\times \\dots \\times b}_{a \\text{ times}}$ as $1$ when $a=0$, but $\\underbrace{b \\times \\dots \\times b}_{a-1 \\text{ times}} \\times b$ otherwise.\n", 169 | "\n", 170 | "This directly translates into the (now familiar-looking) code:" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 26, 176 | "metadata": {}, 177 | "outputs": [ 178 | { 179 | "name": "stdout", 180 | "output_type": "stream", 181 | "text": [ 182 | "4\n", 183 | "2\n", 184 | "16\n" 185 | ] 186 | } 187 | ], 188 | "source": [ 189 | "def pow(b,a):\n", 190 | " if a <= 0:\n", 191 | " return 1\n", 192 | " else:\n", 193 | " return pow(b,a-1) * b\n", 194 | "print(pow(int(input()), int(input())))" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": {}, 200 | "source": [ 201 | "## Drawing recursively\n", 202 | "\n", 203 | "Let us now go back to defining functions to draw pictures, this time using recursion in order to decompose shapes into smaller shapes of the same sort.\n", 204 | "\n", 205 | "### Rectangles\n", 206 | "Let us consider a rectangle. The decomposition we have made so far was based on the idea that a (non-empty) rectangle is indeed just the first row, followed by a rectangle with one less row.\n", 207 | "\n", 208 | "<img src=\"images/rectangle_to_row_rectangle.png\" alt=\"Rectangle to row and rectangle\" style=\"width: 400px;\"/>\n", 209 | "\n", 210 | "This decomposition means therefore that drawing a rectangle recursively will first check whether or not we are dealing with a (degenerate) rectangle with no rows. If that is the case, then we are done and we can return the base case, which will be an empty string. If the rectangle has indeed any rows to draw, we draw the first one as a line, and then the remaining rows by simply recalling drawing a smaller rectangle:" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 2, 216 | "metadata": {}, 217 | "outputs": [ 218 | { 219 | "name": "stdout", 220 | "output_type": "stream", 221 | "text": [ 222 | "3\n", 223 | "5\n", 224 | "*****\n", 225 | "*****\n", 226 | "*****\n", 227 | "\n" 228 | ] 229 | } 230 | ], 231 | "source": [ 232 | "def line(n): return \"\" if n <= 0 else \"*\" + line(n-1)\n", 233 | " \n", 234 | "def rectangle(n,m):\n", 235 | " if n <= 0: \n", 236 | " return \"\"\n", 237 | " else:\n", 238 | " l = line(m)\n", 239 | " r = rectangle(n-1,m)\n", 240 | " return l + \"\\n\" + r\n", 241 | " \n", 242 | "print(rectangle(int(input()),int(input())))" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "metadata": {}, 248 | "source": [ 249 | "We could also observe that a rectangle is not necessarily decomposed as a row, then another rectangle. We could also see a rectangle as just the sum of two smaller rectangles, both roughly half the number of rows of the original. \n", 250 | "\n", 251 | "<img src=\"images/rectangle_to_rectangles.png\" alt=\"Rectangle to two half rectangles\" style=\"width: 600px;\"/>\n", 252 | "\n", 253 | "Of course, care must be taken when splitting in two: we must, at some point, draw the actual rows, otherwise we will end with no drawing at all. For this reason, the following implementation would not draw anything, because it would keep splitting until eventually `n` would become smaller than one and then only the empty string would be returned:\n", 254 | "\n", 255 | "```\n", 256 | "rectangle := (n,m) =>\n", 257 | " if n <= 0 then \n", 258 | " return \"\"\n", 259 | " else\n", 260 | " return rectangle(n//2,m) + \"\\n\" + rectangle(n//2,m)\n", 261 | " \n", 262 | "```\n", 263 | "\n", 264 | "To solve this, we must make sure that we never split a rectangle with only one row. This leads us to two base cases: one for `n = 0`, but also one for `n = 1`:" 265 | ] 266 | }, 267 | { 268 | "cell_type": "code", 269 | "execution_count": 7, 270 | "metadata": {}, 271 | "outputs": [ 272 | { 273 | "name": "stdout", 274 | "output_type": "stream", 275 | "text": [ 276 | "5\n", 277 | "7\n", 278 | "*******\n", 279 | "*******\n", 280 | "*******\n", 281 | "*******\n", 282 | "*******\n", 283 | "\n" 284 | ] 285 | } 286 | ], 287 | "source": [ 288 | "def line(n): return \"\" if n <= 0 else \"*\" + line(n-1)\n", 289 | "\n", 290 | "def rectangle(n,m):\n", 291 | " if n <= 0:\n", 292 | " return \"\"\n", 293 | " elif n == 1:\n", 294 | " return line(m) + \"\\n\"\n", 295 | " else:\n", 296 | " return rectangle(n//2,m) + rectangle(n-n//2,m)\n", 297 | "\n", 298 | "print(rectangle(int(input()),int(input())))" 299 | ] 300 | }, 301 | { 302 | "cell_type": "markdown", 303 | "metadata": {}, 304 | "source": [ 305 | "Also notice that the first sub-rectangle has `n//2` rows, but the second sub-rectangle has `n - n//2` rows. The reason why this is useful is that we know that the two subrectangles together must have exactly `n` rows. We could have used `n//2` for both subrectangles, but this would have led us to issues when `n` is not even. In that case we would need yet another check to make sure that if `n` is odd, then we somehow add an extra row to one of the rectangles, thereby complicating our code needlessly. \n", 306 | "\n", 307 | "The solution proposed in the code above on the other hand, is much simpler. We simply state that the first rectangle will have `n//2` rows, which is reasonable. Then, we state that the second rectangle will have _the remaining rows_. There is no reason to define constructs based on complex reasoning when a much simpler variation ensures the same results without extra effort." 308 | ] 309 | }, 310 | { 311 | "cell_type": "markdown", 312 | "metadata": {}, 313 | "source": [ 314 | "### Triangles\n", 315 | "The exact same reasoning can be applied to drawing triangles, but this time with a minor twist. Suppose we wanted to decompose a triangle by isolating one row per step: depending on which row we remove, the rest of the triangle might stop being a triangle!\n", 316 | "\n", 317 | "<img src=\"images/triangle_to_row_triangle.png\" alt=\"Triangle to row and triangle, both ways\" style=\"width: 600px;\"/>\n", 318 | "\n", 319 | "This means that we are actually forced to decompose the last row away, otherwise we will not be able to re-use the triangle function recursively:" 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": 17, 325 | "metadata": {}, 326 | "outputs": [ 327 | { 328 | "name": "stdout", 329 | "output_type": "stream", 330 | "text": [ 331 | "5\n", 332 | "*\n", 333 | "**\n", 334 | "***\n", 335 | "****\n", 336 | "*****\n", 337 | "\n" 338 | ] 339 | } 340 | ], 341 | "source": [ 342 | "def line(n): return \"\" if n <= 0 else \"*\" + line(n-1)\n", 343 | "\n", 344 | "def triangle(n):\n", 345 | " if n <= 0:\n", 346 | " return \"\"\n", 347 | " else:\n", 348 | " return triangle(n-1) + line(n) + \"\\n\"\n", 349 | "print(triangle(int(input())))" 350 | ] 351 | }, 352 | { 353 | "cell_type": "markdown", 354 | "metadata": {}, 355 | "source": [ 356 | "Suppose on the other hand that, infatuated with the beauty of recursion and the idea of splitting the problem in a balanced way (that is two halves per step), we want to follow the `n//2` strategy for triangles as well.\n", 357 | "\n", 358 | "<img src=\"images/trapeze_to_trapezes.png\" alt=\"Trapeze to trapezes\" style=\"width: 600px;\"/>\n", 359 | "\n", 360 | "It is clear that the splitting always produces two subfigures which are not triangles. Specifically, one of them is called a _trapeze_ (or _trapezoid_). Fortunately, we can see that the upper half (which still looks like a triangle) is a trapeze as well, just a degenerate one in which the upper side is \"collapsed\" to a point.\n", 361 | "\n", 362 | "Armed with this realisation, we can simply define the `trapeze(u,l)` function which draws a trapeze with an upper side of length `u` and a lower side of length `l`:" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": 3, 368 | "metadata": {}, 369 | "outputs": [ 370 | { 371 | "name": "stdout", 372 | "output_type": "stream", 373 | "text": [ 374 | "10\n", 375 | "*\n", 376 | "**\n", 377 | "***\n", 378 | "****\n", 379 | "*****\n", 380 | "******\n", 381 | "*******\n", 382 | "********\n", 383 | "*********\n", 384 | "**********\n", 385 | "\n" 386 | ] 387 | } 388 | ], 389 | "source": [ 390 | "def line(n): return \"\" if n <= 0 else \"*\" + line(n-1)\n", 391 | "\n", 392 | "def trapeze(u,l):\n", 393 | " if l < u:\n", 394 | " return \"\"\n", 395 | " elif l == u:\n", 396 | " return line(u) + \"\\n\"\n", 397 | " else:\n", 398 | " m = (u + l) // 2\n", 399 | " return trapeze(u,m) + trapeze(m+1,l)\n", 400 | " \n", 401 | "def triangle(n):\n", 402 | " return trapeze(1,n)\n", 403 | "\n", 404 | "print(triangle(int(input())))" 405 | ] 406 | }, 407 | { 408 | "cell_type": "code", 409 | "execution_count": null, 410 | "metadata": { 411 | "collapsed": true 412 | }, 413 | "outputs": [], 414 | "source": [] 415 | } 416 | ], 417 | "metadata": { 418 | "kernelspec": { 419 | "display_name": "Python 3", 420 | "language": "python", 421 | "name": "python3" 422 | }, 423 | "language_info": { 424 | "codemirror_mode": { 425 | "name": "ipython", 426 | "version": 3 427 | }, 428 | "file_extension": ".py", 429 | "mimetype": "text/x-python", 430 | "name": "python", 431 | "nbconvert_exporter": "python", 432 | "pygments_lexer": "ipython3", 433 | "version": "3.6.3" 434 | } 435 | }, 436 | "nbformat": 4, 437 | "nbformat_minor": 1 438 | } 439 | -------------------------------------------------------------------------------- /Reader/lecture6.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Higher order functions and partial application" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Our broader goal so far has been to achieve higher and higher levels of abstraction in our code. When programming, we study a domain of application, and translate its ideas into computational concepts (data and functions). In order for the translation to reach maximum usefulness, we need this translation in code to be as close as possible to the original ideas: code must be a _precise_ and _succint_ representation. Precision is important to avoid mistakes, and brevity is needed to avoid losing our train of thought in needless details which are not really present in the original domain, but are spuriously introduced by computational mechanisms. This way, we get to express *as much concepts with as little code as possible*, reducing both the effort it takes us to write our programs and the risk of making mistakes.\n", 15 | "\n", 16 | "In order to achieve this domain-specificity of our code, we will need the ability to encode abstract concepts which do not directly map to concrete operations, but which make the expression of concrete operations much more logical for us to understand and reason about." 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "## Repetita iuvant\n", 24 | "Let us start by observing two functions which are apparently unrelated in what they achieve, but which are based on the same underlying idea: adding numbers and writing lines.\n", 25 | "```\n", 26 | "sum := n =>\n", 27 | " s := 0\n", 28 | " while n > 0:\n", 29 | " s := s + n\n", 30 | " n := n - 1\n", 31 | " return s\n", 32 | "\n", 33 | "line := n =>\n", 34 | " s := \"\"\n", 35 | " while n > 0:\n", 36 | " s := s + \"*\"\n", 37 | " n := n - 1\n", 38 | " return s\n", 39 | "```\n", 40 | "\n", 41 | "Even though the two functions clearly perform very different tasks (one produces a number, the other a line of asterisks), we can observe right away that they both share some structure. Specifically, both functions initialise variable `s` to some initial value (we consider this to be related to the base case of recursive functions), and then they both add a series of values which might depend on the current value of `n`. We can rewrite both functions by abstracting away the differing bits, so that the functions become identical, minus of course different definitions of the base step and the value to add at each iteration:\n", 42 | "\n", 43 | "```\n", 44 | "sum := n =>\n", 45 | " s := BASE\n", 46 | " while n > 0:\n", 47 | " s := s + STEP(n)\n", 48 | " n := n - 1\n", 49 | " return s\n", 50 | "\n", 51 | "line := n =>\n", 52 | " s := BASE\n", 53 | " while n > 0:\n", 54 | " s := s + STEP(n)\n", 55 | " n := n - 1\n", 56 | " return s\n", 57 | "```\n", 58 | "\n", 59 | "The ability to identify similar structures gives an extra possibility: that of building a function that simply encapsulates this similar structure once and for all, so that instead of repeating it every time we can just invoke it (\"refer to it\"). After all, if a concept can be described, implemented, and tested once and for all, there is no point in rediscovering it instead of reusing it every time we need it!\n", 60 | "\n", 61 | "We could call this shared structure `repeat_add`, since it embodies the idea of repeatedly adding values in a loop. This structure (which is no function yet), would look like:\n", 62 | "\n", 63 | "```\n", 64 | "repeat_add := n =>\n", 65 | " s := BASE\n", 66 | " while n > 0:\n", 67 | " s := s + STEP(n)\n", 68 | " n := n - 1\n", 69 | " return s\n", 70 | "```\n", 71 | "\n", 72 | "In order to turn this formulation into a function we would only need to somehow provide `BASE` and `STEP`. Base is clearly a value, so we might simply add it as a parameter. The other argument though is not so simple to handle: `STEP` depends on `n`, and as such we might recognize that it is a function. Fortunately for us, lambda expressions are no more than values, and as such they can be passed around just like normal parameters. This means that we can simply define `repeat_add` as a function as follows:\n", 73 | "\n", 74 | "```\n", 75 | "repeat_add := (n,BASE,STEP) =>\n", 76 | " s := BASE\n", 77 | " while n > 0:\n", 78 | " s := s + STEP(n)\n", 79 | " n := n - 1\n", 80 | " return s\n", 81 | "```\n", 82 | "\n", 83 | "Functions such as `repeat_add` use an important design pattern: they accept other functions as arguments (in this case `STEP`). Functions that accept other functions as arguments are called _higher order functions_, shortened as HOF. We can now instantiate `repeat_add` in order to obtain one-liner definitions of both `sum` and `line`:\n", 84 | "\n", 85 | "```\n", 86 | "sum := n => return repeat_add(n,0,x => return x)\n", 87 | "line := n => return repeat_add(n,\"\",x => return \"*\")\n", 88 | "```\n", 89 | "\n", 90 | "`sum` will simply add each value from `n` down to `1`, starting from `0`. `line` is almost the same: it adds `n` asterisks, starting from the empty string `\"\"`. Reading the definition of both functions now quite clearly states what the functions do, without having to read the much longer code that characterised their first implementation we gave in the beginning.\n", 91 | "\n", 92 | "Moreover, since both `sum` and `line` (in accordance to the original definition, which we want to respect to the letter!) must take only `n` as a parameter, they both accept `n` as input and pass it through to `repeat_add`. Even though it makes sense, this bit of code that does nothing but pass a parameter through looks somewhat inelegant, and indeed it hampers the understandability of the functions. We will work on fixing this later.\n", 93 | "\n", 94 | "Still, it is worthy of notice how, thanks to HOF's, we have achieved something significant: we have built our first function, `repeat_add`, that embodies an abstract concept such as repetition, without necessarily being bound to a concrete use-case. Our toolbox for expressing thought is growing, and when we do need to apply this toolbox to concrete problems (in our case `sum` and `add`), then it is immediately clear how powerful such a toolbox really is." 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "### Repeating actions\n", 102 | "Armed with an extended arsenal for expressing abstract concepts, let us consider another function which is similar to the ones we have just built, yet sufficiently different to force us to take another upward step along the ladder of abstraction: \n", 103 | "\n", 104 | "```\n", 105 | "mul := n =>\n", 106 | " s := 1\n", 107 | " while n > 0:\n", 108 | " s := s * n\n", 109 | " n := n - 1\n", 110 | " return s\n", 111 | "```\n", 112 | "\n", 113 | "Unfortunately this function cannot be implemented in terms of `repeat_add`, because instead of _adding_ the various values together, `mul` needs to multiply them.\n", 114 | "\n", 115 | "In order to cover `mul` as well, we might generalize `repeat_add` even more, so that it simply accepts as input the operation it needs to perform between steps. We will call this operation `MERGE`, since it merges the current value of `s`, accumulated from the previous iterations, with the new value coming from `STEP(n)` which refers to the current iteration. We can see that both `repeat_add` and `mul` both adhere to this new formulation (and since `sum` and `line` are based on `repeat_add`, they can stay untouched):\n", 116 | "\n", 117 | "```\n", 118 | "repeat_add := n =>\n", 119 | " s := BASE\n", 120 | " while n > 0:\n", 121 | " s := MERGE(s, STEP(n))\n", 122 | " n := n - 1\n", 123 | " return s\n", 124 | "\n", 125 | "mul := n =>\n", 126 | " s := BASE\n", 127 | " while n > 0:\n", 128 | " s := MERGE(s, STEP(n))\n", 129 | " n := n - 1\n", 130 | " return s\n", 131 | "```\n", 132 | "\n", 133 | "With this more powerful formulation we can create indeed an even more general `repeat` function that merges `s` and `STEP(n)`. We could also notice that instead of performing the `STEP` and its `MERGE` separately, we can perform them in one go, leading us to:\n", 134 | "\n", 135 | "```\n", 136 | "repeat := (n,BASE,MERGE_STEP) =>\n", 137 | " s := BASE\n", 138 | " while n > 0:\n", 139 | " s := MERGE_STEP(s, n)\n", 140 | " n := n - 1\n", 141 | " return s\n", 142 | "```\n", 143 | "\n", 144 | "We can now redefine `repeat_add` and `mul` by simply providing the proper parameters as follows:\n", 145 | "\n", 146 | "```\n", 147 | "repeat_add := (n,BASE,STEP) => return repeat(n, BASE, (s,n) => return s + STEP(n))\n", 148 | "mul := n => return repeat(n, 1, (s,n) => return s * n)\n", 149 | "```\n", 150 | "\n", 151 | "The `sum` and `line` functions could stay untouched (as they internally refer to `repeat_add`) but we can also redefine them in terms of `repeat`:\n", 152 | "\n", 153 | "```\n", 154 | "sum := n => return repeat(n, 0, (s,n) => return s + n)\n", 155 | "line := n => return repeat(n, \"\", (s,n) => return s + \"*\")\n", 156 | "```\n", 157 | "\n", 158 | "We still have to deal with a minor annoyance though: the way we manage parameter `n`. It is clear that `n` takes on a different role than `BASE`, `STEP`, or `MERGE_STEP`: it remains a parameter *even after those others have been specified*. In order to express this layering of parameters, we can turn `repeat` into a function that further returns another function with `n` as parameter. This is made possible by the fact that functions are simply values, and as such they not only can be passed as parameters, but also returned:\n", 159 | "\n", 160 | "```\n", 161 | "repeat := (BASE,MERGE_STEP) =>\n", 162 | " return n =>\n", 163 | " s := BASE\n", 164 | " while n > 0:\n", 165 | " s = MERGE_STEP(s, n)\n", 166 | " n = n - 1\n", 167 | " return s\n", 168 | "```\n", 169 | "\n", 170 | "Now we can specify `BASE` and `MERGE_STEP` as the first parameters, without being forced to provide a fake `n` which we just pass-through:\n", 171 | "\n", 172 | "```\n", 173 | "repeat_add := (BASE,STEP) => return repeat(BASE, (s,n) => return s + STEP(n))\n", 174 | "sum := repeat(0, (s,n) => return s + n)\n", 175 | "line := repeat(\"\", (s,n) => return s + \"*\")\n", 176 | "mul := repeat(1, (s,n) => return s * n)\n", 177 | "```\n", 178 | "\n", 179 | "Of course this translates quite directly to Python, minus some minor syntax adjustments:" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 2, 185 | "metadata": {}, 186 | "outputs": [ 187 | { 188 | "name": "stdout", 189 | "output_type": "stream", 190 | "text": [ 191 | "4\n", 192 | "10\n", 193 | "****\n", 194 | "10\n", 195 | "****\n", 196 | "24\n" 197 | ] 198 | } 199 | ], 200 | "source": [ 201 | "def repeat(base,merge_step):\n", 202 | " def f(n):\n", 203 | " s = base\n", 204 | " while n > 0:\n", 205 | " s = merge_step(s,n)\n", 206 | " n = n - 1\n", 207 | " return s\n", 208 | " return f\n", 209 | "repeat_add = lambda base,step: repeat(base,lambda s,n:s + step(n))\n", 210 | "\n", 211 | "sum = repeat_add(0, lambda n: n)\n", 212 | "line = repeat_add(\"\", lambda n: \"*\")\n", 213 | "sumR = repeat(0, lambda s,n: s + n)\n", 214 | "lineR = repeat(\"\", lambda s,n: s + \"*\")\n", 215 | "mul = repeat(1, lambda s,n: s * n)\n", 216 | "\n", 217 | "n = int(input())\n", 218 | "print(sum(n))\n", 219 | "print(line(n))\n", 220 | "print(sumR(n))\n", 221 | "print(lineR(n))\n", 222 | "print(mul(n))" 223 | ] 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "metadata": {}, 228 | "source": [ 229 | "## Composing functions\n", 230 | "\n", 231 | "Let us reformulate our original goal: we look for ways of expressing concepts from the real world (or better yet: the internal model that we as human beings have of the real world) into concepts familiar to a programming language. Some real-world concepts are very concrete, such as _Jim's age_ or _drawing a line_. Some real-world concepts, such as _repeating an action n times_, are less concrete, because they do not correspond to a _thing_ or _action_ themselves, but exist on a higher level of abstraction. We could call _repeat_ a _meta-action_ (or HOF).\n", 232 | "\n", 233 | "As our last example of this phenomenon, let us consider the most basic way of combining actions (functions) together. In the real-world we can define the concept of _A then B_, where the meta-action `then` performs action _B_ right after action _A_. The sequentiality implies that whatever _A_ did is made available to _B_ in order for _B_ to perform its own action.\n", 234 | "\n", 235 | "In order to turn this real-world idea into code, we could observe that `then` takes as input two functions, which are then invoked in sequence in order to create a pipeline.\n", 236 | "\n", 237 | "The implementation seems almost trivial:\n", 238 | "\n", 239 | "```\n", 240 | "then := (f,g) => \n", 241 | " return (x => return g(f(x)))\n", 242 | "```\n", 243 | "\n", 244 | "Let us see this in action. Consider simple functions such as:\n", 245 | "\n", 246 | "```\n", 247 | "incr := x => return x + 1\n", 248 | "double := x => return x * 2\n", 249 | "```\n", 250 | "\n", 251 | "Suppose now that we wanted to increment a given number twice. Thanks to `then`, we can simply say:\n", 252 | "\n", 253 | "```\n", 254 | "incr_twice := then(incr,incr)\n", 255 | "```\n", 256 | "\n", 257 | "We could also increment three times:\n", 258 | "\n", 259 | "```\n", 260 | "incr_thrice := then(incr,then(incr,incr))\n", 261 | "```\n", 262 | "\n", 263 | "or even:\n", 264 | "\n", 265 | "```\n", 266 | "incr_thrice := then(incr,incr_twice)\n", 267 | "```\n", 268 | "\n", 269 | "We are clearly not bound to only using the same function multiple times. For example, we could define a function to first increment, and then double a value as:\n", 270 | "\n", 271 | "```\n", 272 | "incr_and_double: then(incr, double)\n", 273 | "```\n", 274 | "\n", 275 | "And so on:\n", 276 | "\n", 277 | "```\n", 278 | "then(incr, then(double, incr))\n", 279 | "```\n", 280 | "\n", 281 | "The power of the `then` operator is unfortunately too easy to understate. This apparently unassuming operator is the key to building (potentially very long) chains or pipelines of functions which simply take over from the previous, perform their action, and then delegate the rest of the work to the next steps in the pipeline. \n", 282 | "\n", 283 | "This ability to transform data from input to output captures the essence of programming, and thus an operator which is central to clearly defining such transformation pipelines is also very close to the heart of programming.\n", 284 | "\n", 285 | "In conclusion, thanks to HOF's, our toolbox is not only growing, but our ability to express and compose abstract concepts is growing as well. Composition is of quintessential importance: without composition, our ability to connect concepts together is limited, and so is our ability to represent _networks of ideas_, which characterize most interesting and articulated problem domains." 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": null, 291 | "metadata": { 292 | "collapsed": true 293 | }, 294 | "outputs": [], 295 | "source": [] 296 | } 297 | ], 298 | "metadata": { 299 | "kernelspec": { 300 | "display_name": "Python 3", 301 | "language": "python", 302 | "name": "python3" 303 | }, 304 | "language_info": { 305 | "codemirror_mode": { 306 | "name": "ipython", 307 | "version": 3 308 | }, 309 | "file_extension": ".py", 310 | "mimetype": "text/x-python", 311 | "name": "python", 312 | "nbconvert_exporter": "python", 313 | "pygments_lexer": "ipython3", 314 | "version": "3.6.1" 315 | } 316 | }, 317 | "nbformat": 4, 318 | "nbformat_minor": 1 319 | } 320 | -------------------------------------------------------------------------------- /Reader/lecture7.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Examples of higher order functions and partial application" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "So far we have discussed some possibilities unlocked by higher order functions and partial application. Such a discussion is, in some sense, theoretical. Limiting one to only the theory, and then stating that \"application is an exercise left to the reader\" with an implication of triviality is inadequate. While it is certainly true that the theory lights the way and shows us the direction of what is indeed possible, it is the daily practice of the engineer that truly turns this potential into reality. Believing that this translation of the ideal into the pragmatic requires little more than faithful knowledge of the theory is childishly naïve at best. \n", 15 | "\n", 16 | "For this reason, we will now focus on building simple but concrete programs with the aforementioned techniques, in order to complement our first intuition with lots of examples.\n", 17 | "\n", 18 | "<div class=\"alert alert-block alert-info\">\n", 19 | "Theory and practice together form a unit which cannot be easily dissolved. Theory defines general patterns, whereas practice concerns itself with the many concrete instances that such patterns represent.\n", 20 | "\n", 21 | "There tends to be much more practice than theory. Take _functions_ as an example. The theory will simply state that functions exist, are defined and work in a given way. The practice of functions is much more complex: we must learn to interpret a real-world phenomenon in terms of functions, define the function itself, give it a name, etc. Moreover, while there exists a limited number (for the sake of this discussion let us say: *just one*) of useful definitions of the concept of functions, at the same time there exist infinite many concrete functions in practice that we might \"type in code\". Some of these are readable and intuitive code, others are readable code but very slow to run, others are unreadable code but very fast to run, and so on.\n", 22 | "\n", 23 | "The apparent chaos inherent to the concrete application of the practice is what leads us to building something of value and of use. Theory brings order to this chaos so that we can see underlying patterns and more effectively work our way through to our goals.\n", 24 | "\n", 25 | "Once again: **neither theory nor practice** can stand without the other.\n", 26 | "</div>" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## A simple case\n", 34 | "\n", 35 | "In the rest of this chapter we will define some recursive higher order functions. We will do so by using almost exclusively the generic combinators `repeat` and `then`. The choice of these combinators is not just the result of a random selection: `repeat` embodies the abstract concept of performing an action multiple times in sequence, whereas `then` chains two (potentially) different actions together into a new, more complex action.\n", 36 | "\n", 37 | "Let us concretize our discussion by observing an example of a simple recursive function which we are now extremely familiar with: the recursive function which draws a line." 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 2, 43 | "metadata": {}, 44 | "outputs": [ 45 | { 46 | "name": "stdout", 47 | "output_type": "stream", 48 | "text": [ 49 | "4\n", 50 | "****\n" 51 | ] 52 | } 53 | ], 54 | "source": [ 55 | "def line(n):\n", 56 | " if n <= 0:\n", 57 | " return \"\"\n", 58 | " else:\n", 59 | " return \"*\" + line(n-1)\n", 60 | "print(line(int(input())))" 61 | ] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "metadata": {}, 66 | "source": [ 67 | "Let us focus now on the expression `\"*\" + line(n-1)` which characterizes the inductive case. We could _interpret_ this as _draw an asterisk, `then` the rest of the line_.\n", 68 | "\n", 69 | "This way of interpreting code does not match the way that code would exactly be read, since we are not talking about arithmetic operations such as the sum of strings. This mismatch between reading and interpreting is not just merely annoying: it can be a source of dissonance between our expectations and what the code really does.\n", 70 | "\n", 71 | "Our goal is to be able to write code that literally matches the description: `then(asterisk, line(n-1))`. This would be read as `asterisk then line(n-1)` if we consider `then` to be a prefix operator, that is an operator which is written to the left of the arguments. Given our current state of the art, this is the best we can do, but moving forward with classes and methods it will be possible to improve on the readability even further.\n", 72 | "\n", 73 | "There is a minor snag along the way: `then` does not work with strings, but rather with functions as parameters. This means that both `asterisk` and `line(n-1)` must return _functions_, which `then` will combine together, instead of just strings. This leads us to reformulating the task we expect of `line` so that it matches this new requirement:\n", 74 | "\n", 75 | "**`line(n)` is a function which takes as input a string, and returns the input string, plus `n` asterisks attached to it**\n", 76 | "\n", 77 | "We could also rephrase this by explicitly taking into account the first parameter to `line` as follows:\n", 78 | "\n", 79 | "**`line` is a function which takes as input a number `n`, and returns a function which further takes as input a string, and returns the input string, plus `n` asterisks attached to it**\n", 80 | "\n", 81 | "This means that `line(3)` does not directly return `\"***\"`. Rather, it returns a function which we can further invoke, with an extra set of parentheses with arguments between them. Thus, `line(3)(\"\")` would yield `\"***\"`. Furthermore, `line(3)(\"...\")` would give us `\"...***\"`, meaning that `line` simply \"keeps drawing\" from a given starting point. This has a major consequence: `line` is now ready to be concatenated to other drawing functions: `then(line(3), line(2))` would give us back a function which adds `\"*****\"` to whatever input it further receives. We can thus keep composing: `then(line(3), then(line(2), line(1)))` would give us back a function which adds `\"******\"` to whatever input it further receives, and so on.\n", 82 | "\n", 83 | "According to this new, composable formulation of drawing:\n", 84 | "- drawing nothing will simply add nothing to the string it receives as input\n", 85 | "- drawing an asterisk will simply add an asterisk to the string it receives as input\n", 86 | "- drawing a newline will simply add a newline to the string it receives as input \n", 87 | "- ...\n", 88 | "\n", 89 | "We can turn this in code quite straightforwardly:" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 3, 95 | "metadata": {}, 96 | "outputs": [ 97 | { 98 | "name": "stdout", 99 | "output_type": "stream", 100 | "text": [ 101 | "\n", 102 | "...\n", 103 | "*\n", 104 | "...*\n" 105 | ] 106 | } 107 | ], 108 | "source": [ 109 | "def nothing(s): return s\n", 110 | "def asterisk(s): return s + \"*\"\n", 111 | "def space(s): return s + \" \"\n", 112 | "def newline(s): return s + \"\\n\"\n", 113 | "\n", 114 | "print(nothing(\"\"))\n", 115 | "print(nothing(\"...\"))\n", 116 | "\n", 117 | "print(asterisk(\"\"))\n", 118 | "print(asterisk(\"...\"))" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "This composability makes it easy, or at least facilitates, drawing multiple things in combination. For example, if we wanted to draw multiple asterisks in sequence, we could call the asterisk function nested within itself: the result of the first is then fed right into the second:" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 3, 131 | "metadata": {}, 132 | "outputs": [ 133 | { 134 | "name": "stdout", 135 | "output_type": "stream", 136 | "text": [ 137 | "**\n", 138 | "***\n" 139 | ] 140 | } 141 | ], 142 | "source": [ 143 | "print(asterisk(asterisk(\"\")))\n", 144 | "print(asterisk(asterisk(asterisk(\"\"))))" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "The example we have just given is a bit weak. It requires us to provide an initial string always, which might be something we want to delay for later. Moreover, the combination of drawing actions follows a *reverse* order with respect to the one we read them into. We are used to read from left to right, so it would come natural to assume that the first call to the `asterisk` function draws the first asterisk, the second call draws the second one, the third call draws the third one, and finally the empty string is the last to be drawn. Instead, given how we built the definition of `asterisk` (and all the other similar functions), the empty string is what is drawn first (that is, the leftmost symbol being drawn), and the other asterisks follow in reverse order (that is, the left-most call to `asterisk` is the one that draws the right-most asterisk).\n", 152 | "We can observe this phenomen even more clearly in the following example:" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 4, 158 | "metadata": {}, 159 | "outputs": [ 160 | { 161 | "name": "stdout", 162 | "output_type": "stream", 163 | "text": [ 164 | "*321\n" 165 | ] 166 | } 167 | ], 168 | "source": [ 169 | "def one(s) : return s + \"1\"\n", 170 | "def two(s): return s + \"2\"\n", 171 | "def three(s): return s + \"3\"\n", 172 | "print(one(two(three(asterisk(\"\")))))" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "metadata": {}, 178 | "source": [ 179 | "From reading the code above, it would be more intuitive for us if the output was `123*`, but instead we get `*321`.\n", 180 | "\n", 181 | "Since invoking a function with the output of another one (and in the more intuitive order for a human reader) is exactly what we built `then` for, we could rephrase the code above in terms of `then`:" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 5, 187 | "metadata": {}, 188 | "outputs": [ 189 | { 190 | "name": "stdout", 191 | "output_type": "stream", 192 | "text": [ 193 | "123*\n" 194 | ] 195 | } 196 | ], 197 | "source": [ 198 | "def then(f,g): \n", 199 | " return lambda x: g(f(x))\n", 200 | "\n", 201 | "print(then(one, then(two, then(three, asterisk)))(\"\"))" 202 | ] 203 | }, 204 | { 205 | "cell_type": "markdown", 206 | "metadata": {}, 207 | "source": [ 208 | "Using the function `then`, the output is now `123*` which is much more intuitive.\n", 209 | "\n", 210 | "Below are shown a few more examles using `then`:" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 5, 216 | "metadata": {}, 217 | "outputs": [ 218 | { 219 | "name": "stdout", 220 | "output_type": "stream", 221 | "text": [ 222 | "**\n", 223 | "***\n", 224 | "!!!***\n", 225 | "* *\n" 226 | ] 227 | } 228 | ], 229 | "source": [ 230 | "def then(f,g): \n", 231 | " return lambda x: g(f(x))\n", 232 | "\n", 233 | "two_asterisks = then(asterisk, asterisk)\n", 234 | "three_asterisks = then(asterisk, two_asterisks)\n", 235 | "\n", 236 | "print(two_asterisks(\"\"))\n", 237 | "print(three_asterisks(\"\"))\n", 238 | "print(three_asterisks(\"!!!\"))\n", 239 | "print(then(asterisk, then(space, asterisk))(\"\"))" 240 | ] 241 | }, 242 | { 243 | "cell_type": "markdown", 244 | "metadata": {}, 245 | "source": [ 246 | "We have now shifted our focus from the very concrete idea of \"concrete drawing\", in which functions would directly output their drawing, to something more abstract. Our notion of \"abstract drawing\" delays the concrete operation, and simply accumulates instructions on _how to draw_. These instructions constitute a pipeline which, until it gets some initial string to draw from, performs no operation whatsoever.\n", 247 | "\n", 248 | "Such a pipeline acts in a way comparable to the familiar rules of arithmetics. For example, we could notice that just like `0 + n` is simply the same as `n`, `then(nothing, x)` will simply be the same as `x`. This basic fact suggests that drawing is no more than a fancy form of arithmetics on pictures, and that there is a correspondence between this \"concatenative structure\" and other similar structures such as strings:" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": 5, 254 | "metadata": {}, 255 | "outputs": [ 256 | { 257 | "name": "stdout", 258 | "output_type": "stream", 259 | "text": [ 260 | "*\n", 261 | "*\n" 262 | ] 263 | } 264 | ], 265 | "source": [ 266 | "print(then(nothing, asterisk)(\"\"))\n", 267 | "print(then(asterisk, nothing)(\"\"))" 268 | ] 269 | }, 270 | { 271 | "cell_type": "markdown", 272 | "metadata": {}, 273 | "source": [ 274 | "At this point, we can \"easily\" assemble the `line` function:\n", 275 | "- an empty line simply adds `nothing`, as there is indeed nothing to do;\n", 276 | "- a non-empty line first adds an `asterisk`, `then` the rest of the `line`." 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 6, 282 | "metadata": {}, 283 | "outputs": [ 284 | { 285 | "name": "stdout", 286 | "output_type": "stream", 287 | "text": [ 288 | "4\n" 289 | ] 290 | }, 291 | { 292 | "data": { 293 | "text/plain": [ 294 | "****" 295 | ] 296 | }, 297 | "execution_count": 6, 298 | "metadata": {}, 299 | "output_type": "execute_result" 300 | } 301 | ], 302 | "source": [ 303 | "def then(f,g): return lambda x: g(f(x))\n", 304 | "\n", 305 | "def nothing(s): return s\n", 306 | "def asterisk(s): return s + \"*\"\n", 307 | "\n", 308 | "def line(n):\n", 309 | " if n <= 0:\n", 310 | " return nothing\n", 311 | " else:\n", 312 | " return then(asterisk, line(n-1))\n", 313 | "\n", 314 | "print(line(int(input()))(\"\"))" 315 | ] 316 | }, 317 | { 318 | "cell_type": "markdown", 319 | "metadata": {}, 320 | "source": [ 321 | "### More complex figures\n", 322 | "\n", 323 | "Let us move on to more complex figures. In particular, the first such figure we encounter when climbing up in complexity is the rectangle. According to the usual definition, a rectangle with `n` rows and `m` columns is drawn as:\n", 324 | "- `nothing` if `n` is zero;\n", 325 | "- a `line`, then a `newline`, and then a `rectangle` with n-1 rows otherwise.\n", 326 | "\n", 327 | "With our framework, this is translated quite straightforwardly as:" 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": 7, 333 | "metadata": {}, 334 | "outputs": [ 335 | { 336 | "name": "stdout", 337 | "output_type": "stream", 338 | "text": [ 339 | "4\n", 340 | "5\n", 341 | "*****\n", 342 | "*****\n", 343 | "*****\n", 344 | "*****\n", 345 | "\n" 346 | ] 347 | } 348 | ], 349 | "source": [ 350 | "def then(f,g): return lambda x: g(f(x))\n", 351 | "\n", 352 | "def nothing(s): return s\n", 353 | "def asterisk(s): return s + \"*\"\n", 354 | "def newline(s): return s + \"\\n\"\n", 355 | "\n", 356 | "def line(n):\n", 357 | " if n <= 0:\n", 358 | " return nothing\n", 359 | " else:\n", 360 | " return then(asterisk, line(n-1))\n", 361 | "\n", 362 | "def rectangle(n,m):\n", 363 | " if n <= 0:\n", 364 | " return nothing\n", 365 | " else:\n", 366 | " return then(then(line(m), newline), rectangle(n-1,m))\n", 367 | " \n", 368 | "n = int(input())\n", 369 | "m = int(input())\n", 370 | "print(rectangle(n,m)(\"\"))" 371 | ] 372 | }, 373 | { 374 | "cell_type": "markdown", 375 | "metadata": {}, 376 | "source": [ 377 | "#### Repeating\n", 378 | "\n", 379 | "We now start noticing a higher level pattern: we are indeed just repeating some (drawing) action multiple times. We might be (and indeed are!) tempted to capture this higher level pattern with an own name and as an own function.\n", 380 | "\n", 381 | "This function is the also familiar `repeat`, which we could reformulate as a function which accepts a function as input, and then executes it `n` times starting from a given input.\n", 382 | "\n", 383 | "`repeat(f,n)` will thus yield $\\lambda x : \\underbrace{f(f(...(f(x))))}_{n \\text{ times}}$.\n", 384 | "\n", 385 | "Note that $\\lambda$ is how the actual greek letter that we read as `lambda` is written.\n", 386 | "\n", 387 | "We can implement `repeat` now easily as a function which:\n", 388 | "- returns `nothing` if `n` is zero;\n", 389 | "- performs `f` once, `then` it `repeat`s `f` `n-1` times otherwise." 390 | ] 391 | }, 392 | { 393 | "cell_type": "code", 394 | "execution_count": 5, 395 | "metadata": {}, 396 | "outputs": [ 397 | { 398 | "name": "stdout", 399 | "output_type": "stream", 400 | "text": [ 401 | "4\n", 402 | "16\n" 403 | ] 404 | } 405 | ], 406 | "source": [ 407 | "def repeat(f,n):\n", 408 | " if n <= 0:\n", 409 | " return nothing\n", 410 | " else:\n", 411 | " return then(f, repeat(f, n-1))\n", 412 | "\n", 413 | "print(repeat(lambda x: x * 2, int(input()))(1))" 414 | ] 415 | }, 416 | { 417 | "cell_type": "markdown", 418 | "metadata": {}, 419 | "source": [ 420 | "We could even redefine a compacter and more elegant version of `line` by means of `repeat`:" 421 | ] 422 | }, 423 | { 424 | "cell_type": "code", 425 | "execution_count": 8, 426 | "metadata": {}, 427 | "outputs": [ 428 | { 429 | "name": "stdout", 430 | "output_type": "stream", 431 | "text": [ 432 | "3\n", 433 | "***\n" 434 | ] 435 | } 436 | ], 437 | "source": [ 438 | "def line(n): return repeat(asterisk, n)\n", 439 | "\n", 440 | "print(line(int(input()))(\"\"))" 441 | ] 442 | }, 443 | { 444 | "cell_type": "markdown", 445 | "metadata": {}, 446 | "source": [ 447 | "In the exact same spirit, a rectangle will just become the repetition of lines (each followed by a newline of course):" 448 | ] 449 | }, 450 | { 451 | "cell_type": "code", 452 | "execution_count": 10, 453 | "metadata": {}, 454 | "outputs": [ 455 | { 456 | "name": "stdout", 457 | "output_type": "stream", 458 | "text": [ 459 | "2\n", 460 | "3\n", 461 | "***\n", 462 | "***\n", 463 | "\n" 464 | ] 465 | } 466 | ], 467 | "source": [ 468 | "def rectangle(n,m): return repeat(then(line(m), newline), n)\n", 469 | "\n", 470 | "print(rectangle(int(input()), int(input()))(\"\"))" 471 | ] 472 | }, 473 | { 474 | "cell_type": "markdown", 475 | "metadata": {}, 476 | "source": [ 477 | "Finally, let us build hollow squares. Hollow squares are built up from hollow lines, but for the rest follow the same approach:" 478 | ] 479 | }, 480 | { 481 | "cell_type": "code", 482 | "execution_count": 11, 483 | "metadata": {}, 484 | "outputs": [ 485 | { 486 | "name": "stdout", 487 | "output_type": "stream", 488 | "text": [ 489 | "4\n", 490 | "****\n", 491 | "* *\n", 492 | "* *\n", 493 | "****\n" 494 | ] 495 | } 496 | ], 497 | "source": [ 498 | "def hollow_line(n): return then(asterisk, then(repeat(space, n-2), asterisk))\n", 499 | "\n", 500 | "def hollow_square(n):\n", 501 | " return then(then(line(n), newline), then(repeat(then(hollow_line(n), newline), n-2), line(n)))\n", 502 | "\n", 503 | "print(hollow_square(int(input()))(\"\"))" 504 | ] 505 | }, 506 | { 507 | "cell_type": "markdown", 508 | "metadata": {}, 509 | "source": [ 510 | "## Wrapping up\n", 511 | "The approach we have just described falls under the broader name of _declarative programming_. Declarative programming is not a rigidly defined construct, and actually it is more of an umbrella concept which we use to categorize implementations of programs that follow the same general style.\n", 512 | "\n", 513 | "Usually, a style of programming is defined as declarative if, instead of focusing on the update of variables in sequence in order to achieve a goal, we simply state the goal in code in a way that is read as almost natural language, and then via various means of composition the right answer is found. \n", 514 | "\n", 515 | "Declarative programming therefore allows us to quickly define solutions which are at a higher level of abstraction, and which therefore offer more chances for correctness than the imperative version of the same program. Working at a higher level of abstraction offers us the possibility to express our thoughts directly, unfiltered, in code, instead of performing a complex, cumbersome translation which might itself be a source of errors." 516 | ] 517 | } 518 | ], 519 | "metadata": { 520 | "kernelspec": { 521 | "display_name": "Python 3", 522 | "language": "python", 523 | "name": "python3" 524 | }, 525 | "language_info": { 526 | "codemirror_mode": { 527 | "name": "ipython", 528 | "version": 3 529 | }, 530 | "file_extension": ".py", 531 | "mimetype": "text/x-python", 532 | "name": "python", 533 | "nbconvert_exporter": "python", 534 | "pygments_lexer": "ipython3", 535 | "version": "3.6.3" 536 | } 537 | }, 538 | "nbformat": 4, 539 | "nbformat_minor": 1 540 | } 541 | -------------------------------------------------------------------------------- /Reader/lecture8.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Algebra of drawing functions" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "In this chapter we will close the circle of functions, by showing that functions can also be used to represent complex data structures and their aggregation. The naïve interpretation of functions imposes a somewhat false dichotomy between state and actions, where functions are merely relegated to containers of actions.\n", 15 | "\n", 16 | "A broader interpretation of functions would begin by realising that the state itself can be easily modeled as a function. For example, there is no actual difference between a number, such as `3`, and a function that always returns that number, such as `three := () => 3`. The fact that the function definition will first require a call does not change the fact that it merely acts as a container for `3`, and as such the two definitions are equivalent. Moreover, given any piece of state `C`, we could encapsulate it in a function `() => C` which does nothing but returning its value. This means that for every value we could possibly conceive, there exists a function which sole purpose is to return it. This means that `State <= Functions`. The opposite is not true. Simplifying broadly, we can easily imagine that even if we managed to encode functions cleverly in the form of variables, we would still at some point need functions in order to evaluate such an encoding, or _call the function_ as it were. \n", 17 | "\n", 18 | "Thus, we can conclude that functions are the fundamental unit of computation, and even more so the only construct we should really be focusing on. Indeed, even concepts such as object-orientation, inheritance, and much more are trivial to encode and represent only in terms of functions and their composition. We will see more of this in the coming units, but in the rest of this chapter we will show how to build a simple (but not trivial) data structure representing pictures only by means of functions." 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "## A general drawing function\n", 26 | "\n", 27 | "Let us go back to the simplest form of drawing functions: just two nested loops, and at each iteration we simply check whether or not the current pixel should be \"lit\" (an asterisk) or not (a space). The check will need to know the total size of the figure being drawn (width and height) plus the coordinates of the current pixel (x and y, or j and i).\n", 28 | "\n", 29 | "Depending on the check we perform at each pixel, we could draw completely different figures, but the general shape of the drawing function will not change. For this reason we can build the general concept, without any mention of the specific figure being drawn, as a higher order function which accepts the check per pixel as a parameter:" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 1, 35 | "metadata": { 36 | "collapsed": true 37 | }, 38 | "outputs": [], 39 | "source": [ 40 | "def draw(n,m,p):\n", 41 | " s = \"\"\n", 42 | " i = 0\n", 43 | " while i < n:\n", 44 | " j = 0\n", 45 | " while j < m:\n", 46 | " if p(n,m,i,j):\n", 47 | " s = s + \"*\"\n", 48 | " else:\n", 49 | " s = s + \" \"\n", 50 | " j = j + 1\n", 51 | " s = s + \"\\n\"\n", 52 | " i = i + 1\n", 53 | " return s" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "The `draw` function accepts as arguments the size of the figure to draw, plus a function `p` (stands for \"picture\"), which determines for each pixel whether or not it will be drawn.\n", 61 | "\n", 62 | "This function `p` accepts as arguments the size of the picture to draw, which we will usually call `n` and `m` (height and width respectively), and of course the coordinates of the current pixel, which we will usually call `i` and `j` (row and column respectively). By checking the coordinates against each other and against the size of the picture, the function determines relevant properties of the pixel (such as being on the left side, or `j == 0`) and concludes whether or not the pixel must be lit.\n", 63 | "\n", 64 | "For example, by using a function which always returns `True`, we get a full square; by using a function which always returns `False`, we get an \"invisible\" square; if we use a function which checks whether or not `j <= i`, we only keep the lower right triangle on; and so on..." 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 18, 70 | "metadata": {}, 71 | "outputs": [ 72 | { 73 | "name": "stdout", 74 | "output_type": "stream", 75 | "text": [ 76 | "***\n", 77 | "***\n", 78 | "***\n", 79 | "\n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | "\n", 84 | "* \n", 85 | "** \n", 86 | "***\n", 87 | "\n" 88 | ] 89 | } 90 | ], 91 | "source": [ 92 | "print(draw(3,3,lambda n,m,i,j: True))\n", 93 | "print(draw(3,3,lambda n,m,i,j: False))\n", 94 | "print(draw(3,3,lambda n,m,i,j: j <= i))" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "At this point we could observe a powerful fact: **any function from four integers (width, height, row index, column index) into a boolean (pixel on/off) is a figure**. There is no function which corresponds to no figure, since every such function can be drawn. Perhaps some of these functions do not produce any _interesting_ figure, but it will be possible to draw it nonetheless.\n", 102 | "\n", 103 | "We need no complex data structure to define figures, as we might expect with an object-oriented background where an inheritance hierarchy of classes is used to define a taxonomy of figures: functions are just enough." 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "### Combinators\n", 111 | "\n", 112 | "Let us now consider a hollow square. We can easily identify and draw the borders of the square, which are clearly the components of the picture.\n", 113 | "\n", 114 | "Specifically, the borders are characterized by the following checks: `i == 0`, `j == 0`, `i == n-1`, or `j == n-1`. We can easily fill in the gaps and define the lambdas as follows:" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 19, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "name": "stdout", 124 | "output_type": "stream", 125 | "text": [ 126 | "***\n", 127 | " \n", 128 | " \n", 129 | "\n", 130 | "* \n", 131 | "* \n", 132 | "* \n", 133 | "\n", 134 | " \n", 135 | " \n", 136 | "***\n", 137 | "\n", 138 | " *\n", 139 | " *\n", 140 | " *\n", 141 | "\n" 142 | ] 143 | } 144 | ], 145 | "source": [ 146 | "print(draw(3,3,lambda n,m,i,j: i == 0))\n", 147 | "print(draw(3,3,lambda n,m,i,j: j == 0))\n", 148 | "print(draw(3,3,lambda n,m,i,j: i == n-1))\n", 149 | "print(draw(3,3,lambda n,m,i,j: j == m-1))" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "metadata": {}, 155 | "source": [ 156 | "Of course now we need a way to compose these borders together in a single figure. We are looking for a way to compose two figures into a figure which is their union. Let us rephrase this: we are looking for a way to compose two functions into a function which itself returns `True` when any of the two original functions would have returned `True`.\n", 157 | "\n", 158 | "Let us begin by doing this explicitly and a bit too verbosely:" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 2, 164 | "metadata": {}, 165 | "outputs": [ 166 | { 167 | "name": "stdout", 168 | "output_type": "stream", 169 | "text": [ 170 | "***\n", 171 | "* *\n", 172 | "***\n", 173 | "\n" 174 | ] 175 | } 176 | ], 177 | "source": [ 178 | "upper_line = lambda n,m,i,j: i == 0\n", 179 | "left_line = lambda n,m,i,j: j == 0\n", 180 | "lower_line = lambda n,m,i,j: i == n-1\n", 181 | "right_line = lambda n,m,i,j: j == m-1\n", 182 | "\n", 183 | "hollow_square = lambda n,m,i,j: upper_line(n,m,i,j) or lower_line(n,m,i,j) or left_line(n,m,i,j) or right_line(n,m,i,j)\n", 184 | "print(draw(3,3,hollow_square))" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": {}, 190 | "source": [ 191 | "Suppose now that we wanted to just draw the upper and lower rows together. This would mean we would have to define another function, and either figure out a reasonable name for it or _curry_ a cumbersome lambda around:" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": 3, 197 | "metadata": {}, 198 | "outputs": [ 199 | { 200 | "name": "stdout", 201 | "output_type": "stream", 202 | "text": [ 203 | "***\n", 204 | " \n", 205 | "***\n", 206 | "\n" 207 | ] 208 | } 209 | ], 210 | "source": [ 211 | "upper_and_lower_rows = lambda n,m,i,j: upper_line(n,m,i,j) or lower_line(n,m,i,j)\n", 212 | "print(draw(3,3,upper_and_lower_rows))" 213 | ] 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "metadata": {}, 218 | "source": [ 219 | "The unpleasant verbosity comes from the repetition of `n,m,i,j`: this tuple of four integers is repeated _three times!_ in a single row.\n", 220 | "\n", 221 | "We can get rid of this verbosity by observing that we do not really wish to think about the parameters, and our target code should look more like `union(upper_line, lower_line)`, which clearly reflects our thoughts.\n", 222 | "\n", 223 | "Such pattern could be defined explicitly as a higher order function which takes the two figures as parameters, invokes both of them, and combines the results with an `or`:" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": 4, 229 | "metadata": {}, 230 | "outputs": [ 231 | { 232 | "name": "stdout", 233 | "output_type": "stream", 234 | "text": [ 235 | "***\n", 236 | " \n", 237 | "***\n", 238 | "\n" 239 | ] 240 | } 241 | ], 242 | "source": [ 243 | "def union(p,q):\n", 244 | " return lambda n,m,i,j: p(n,m,i,j) or q(n,m,i,j)\n", 245 | "print(draw(3,3, union(upper_line, lower_line)))" 246 | ] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "metadata": {}, 251 | "source": [ 252 | "We call higher order functions such as `union` _combinators_, since they _combine_ arbitrary complex data structures together. Thanks to combinators, it is usually possible to very quickly assemble existing structures into new forms, and all the while retaining elegant and expressive code: " 253 | ] 254 | }, 255 | { 256 | "cell_type": "code", 257 | "execution_count": 22, 258 | "metadata": {}, 259 | "outputs": [ 260 | { 261 | "name": "stdout", 262 | "output_type": "stream", 263 | "text": [ 264 | "***\n", 265 | "* \n", 266 | "* \n", 267 | "\n", 268 | "* *\n", 269 | "** **\n", 270 | "******\n", 271 | "******\n", 272 | "******\n", 273 | "******\n", 274 | "\n" 275 | ] 276 | } 277 | ], 278 | "source": [ 279 | "print(draw(3,3,union(upper_line, left_line)))\n", 280 | "print(draw(6,6,union(lambda n,m,i,j: n-i-1 <= j, lambda n,m,i,j: j <= i)))" 281 | ] 282 | }, 283 | { 284 | "cell_type": "markdown", 285 | "metadata": {}, 286 | "source": [ 287 | "Moreover, good combinators can nest arbitrarily deep. Indeed, the result of `union` is itself a figure, therefore we can just pass it around as a figure, even as a parameter to another `union` itself. The ability to arbitrarily nest is very important, as it guarantees that there is no artificial upper bound to our ability to express deeply articulated concepts. \n", 288 | "\n", 289 | "Indeed, how could we possibly guarantee that no interesting figure exists which requires more than a fixed level of nested combinations?\n", 290 | "\n", 291 | "Such a nested combination would perform the union of the horizontal borders and the vertical borders, in order to produce a succint and semantically recognizable definition of hollow squares:" 292 | ] 293 | }, 294 | { 295 | "cell_type": "code", 296 | "execution_count": 23, 297 | "metadata": {}, 298 | "outputs": [ 299 | { 300 | "name": "stdout", 301 | "output_type": "stream", 302 | "text": [ 303 | "*****\n", 304 | "* *\n", 305 | "* *\n", 306 | "* *\n", 307 | "*****\n", 308 | "\n" 309 | ] 310 | } 311 | ], 312 | "source": [ 313 | "print(draw(5,5, union(union(upper_line, lower_line), union(left_line, right_line))))" 314 | ] 315 | }, 316 | { 317 | "cell_type": "markdown", 318 | "metadata": {}, 319 | "source": [ 320 | "Of course `union` is not the only combinator possible. Instead of unstructuredly exploring combinators by following our experience and intuition, we could simply observe that since the figure functions all return boolean values, we can merely perform all operations that are allowed on booleans on their return types, therefore leading us to multiple combinators without having to think much. We have already seen `or`, which led us to the `union`. We call this the _lifting_ of a boolean operator from the (concreter) domain of booleans into the (abstracter) domain of figure functions. \n", 321 | "\n", 322 | "<div class=\"alert alert-block alert-info\">\n", 323 | "Lifting here refers to moving higher up, and of course assumes the intuition that abstraction grows upwards. We could just turn everything upside down and image that abstraction lies at the bottom, but then we would need to say _digging_ instead of _lifting_, which is now widely accepted as a common idiom. Nevertheless, it is all quite arbitrary.\n", 324 | "</div>\n", 325 | "\n", 326 | "By further generalizing the `union`, we could notice that all binary combinators (binary in the sense of accepting two figures `p` and `q` as input) might look like:\n", 327 | "\n", 328 | "```\n", 329 | "def COMBINATOR(p,q):\n", 330 | " return lambda n,m,i,j: p(n,m,i,j) BOOLEAN_OPERATION q(n,m,i,j)\n", 331 | "```\n", 332 | "\n", 333 | "For example, we could perform the `and` of the result of the figure parameter(s), thereby obtaining the intersection of figures. Similarly, we could try the same strategy on unary operators such as `not`, leading us to the inversion of figures:" 334 | ] 335 | }, 336 | { 337 | "cell_type": "code", 338 | "execution_count": 11, 339 | "metadata": {}, 340 | "outputs": [ 341 | { 342 | "name": "stdout", 343 | "output_type": "stream", 344 | "text": [ 345 | "* \n", 346 | " \n", 347 | " \n", 348 | "\n", 349 | "***\n", 350 | "* \n", 351 | "* \n", 352 | "\n", 353 | " **\n", 354 | " **\n", 355 | " **\n", 356 | "\n" 357 | ] 358 | } 359 | ], 360 | "source": [ 361 | "def pic_and(p,q):\n", 362 | " return lambda n,m,i,j: p(n,m,i,j) and q(n,m,i,j)\n", 363 | "print(draw(3,3,pic_and(upper_line, left_line)))\n", 364 | "\n", 365 | "def pic_or(p,q):\n", 366 | " return union(p,q)\n", 367 | "print(draw(3,3,pic_or(upper_line, left_line)))\n", 368 | "\n", 369 | "def pic_not(p):\n", 370 | " return lambda n,m,i,j: not p(n,m,i,j)\n", 371 | "print(draw(3,3,pic_not(left_line)))" 372 | ] 373 | }, 374 | { 375 | "cell_type": "markdown", 376 | "metadata": {}, 377 | "source": [ 378 | "Manipulating multiple return values with boolean operators is one strategy for combining and transforming figures. We can of course also perform some transformation of the parameters, for example with offsets, scales, or other transformations. Here we only show the flipping transformation which horizontally or vertically turns a picture around its middle axis.\n", 379 | "\n", 380 | "This is simply done by, for example, invoking the actual figure function with `n-1` instead of `0`, `n-2` instead of `1`, etc." 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": 12, 386 | "metadata": {}, 387 | "outputs": [ 388 | { 389 | "name": "stdout", 390 | "output_type": "stream", 391 | "text": [ 392 | "***\n", 393 | "** \n", 394 | "* \n", 395 | "\n", 396 | "***\n", 397 | " **\n", 398 | " *\n", 399 | "\n", 400 | "* \n", 401 | "** \n", 402 | "***\n", 403 | "\n", 404 | " *\n", 405 | " **\n", 406 | "***\n", 407 | "\n" 408 | ] 409 | } 410 | ], 411 | "source": [ 412 | "def flip_hor(p):\n", 413 | " return lambda n,m,i,j: p(n,m,i,m-1-j)\n", 414 | "def flip_ver(p):\n", 415 | " return lambda n,m,i,j: p(n,m,n-1-i,j)\n", 416 | "upper_right_triangle = lambda n,m,i,j: i <= j\n", 417 | "upper_left_triangle = flip_hor(upper_right_triangle)\n", 418 | "lower_right_triangle = flip_ver(upper_right_triangle)\n", 419 | "lower_left_triangle = flip_ver(upper_left_triangle)\n", 420 | "print(draw(3,3,upper_left_triangle))\n", 421 | "print(draw(3,3,upper_right_triangle))\n", 422 | "print(draw(3,3,lower_left_triangle))\n", 423 | "print(draw(3,3,lower_right_triangle))" 424 | ] 425 | }, 426 | { 427 | "cell_type": "markdown", 428 | "metadata": {}, 429 | "source": [ 430 | "Complex shapes which required quite a lot of thought, such as the pyramid, can now simply be decomposed into simpler shapes such as two lower triangles. Flipping helps us also in the building of variations of the same figure, such as a rotated pyramid, which would have required radically different code (with respect to the horizontal pyramid) to build. \n", 431 | "\n", 432 | "<div class=\"alert alert-block alert-info\">\n", 433 | "This mismatch in the code that implements similar figures in very different ways (such as horizontal and vertical pyramids) can become a very serious issue: it severly limits our ability to quickly experiment with related concepts, and therefore limits our ability to build flexible and robust software. \n", 434 | "</div>" 435 | ] 436 | }, 437 | { 438 | "cell_type": "code", 439 | "execution_count": 13, 440 | "metadata": {}, 441 | "outputs": [ 442 | { 443 | "name": "stdout", 444 | "output_type": "stream", 445 | "text": [ 446 | " \n", 447 | " \n", 448 | " * \n", 449 | " *** \n", 450 | "*****\n", 451 | "\n", 452 | "* \n", 453 | "** \n", 454 | "*** \n", 455 | "** \n", 456 | "* \n", 457 | "\n", 458 | "* *\n", 459 | "** **\n", 460 | "*****\n", 461 | "** **\n", 462 | "* *\n", 463 | "\n" 464 | ] 465 | } 466 | ], 467 | "source": [ 468 | "upper_pyramid = pic_and(upper_left_triangle, upper_right_triangle)\n", 469 | "lower_pyramid = pic_and(lower_left_triangle, lower_right_triangle)\n", 470 | "left_pyramid = pic_and(lower_left_triangle, upper_left_triangle)\n", 471 | "right_pyramid = pic_and(lower_right_triangle, upper_right_triangle)\n", 472 | "print(draw(5,5,lower_pyramid))\n", 473 | "print(draw(5,5,left_pyramid))\n", 474 | "\n", 475 | "\n", 476 | "butterfly = pic_or(left_pyramid, right_pyramid)\n", 477 | "print(draw(5,5,butterfly))" 478 | ] 479 | }, 480 | { 481 | "cell_type": "markdown", 482 | "metadata": {}, 483 | "source": [ 484 | "We can even put useful formulae to the test in order to create new shapes. A good example of this could be using Pythagora's formula in order to draw circles:" 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": 14, 490 | "metadata": {}, 491 | "outputs": [ 492 | { 493 | "name": "stdout", 494 | "output_type": "stream", 495 | "text": [ 496 | " *** \n", 497 | "*****\n", 498 | "*****\n", 499 | "*****\n", 500 | " *** \n", 501 | "\n" 502 | ] 503 | } 504 | ], 505 | "source": [ 506 | "circle = lambda n,m,i,j: (i-n//2)*(i-n//2)+(j-n//2)*(j-n//2) <= n*n//4\n", 507 | "print(draw(5,5,circle))" 508 | ] 509 | }, 510 | { 511 | "cell_type": "markdown", 512 | "metadata": {}, 513 | "source": [ 514 | "By combining circles and other figures we can even draw some recognizable figures quite easily:" 515 | ] 516 | }, 517 | { 518 | "cell_type": "code", 519 | "execution_count": 16, 520 | "metadata": {}, 521 | "outputs": [ 522 | { 523 | "name": "stdout", 524 | "output_type": "stream", 525 | "text": [ 526 | " ***** \n", 527 | " ********* \n", 528 | " ********** \n", 529 | " ****** *** \n", 530 | " ********* \n", 531 | "********* \n", 532 | "******** \n", 533 | "******* \n", 534 | "******** \n", 535 | "********* \n", 536 | " ********* \n", 537 | " ********** \n", 538 | " ********** \n", 539 | " ********* \n", 540 | " ***** \n", 541 | "\n" 542 | ] 543 | } 544 | ], 545 | "source": [ 546 | "eye = lambda n,m,i,j: i == n//4 and j == m//2\n", 547 | "mouth = right_pyramid\n", 548 | "print(draw(15,15,pic_and(circle, pic_not(pic_or(eye, mouth)))))" 549 | ] 550 | } 551 | ], 552 | "metadata": { 553 | "kernelspec": { 554 | "display_name": "Python 3", 555 | "language": "python", 556 | "name": "python3" 557 | }, 558 | "language_info": { 559 | "codemirror_mode": { 560 | "name": "ipython", 561 | "version": 3 562 | }, 563 | "file_extension": ".py", 564 | "mimetype": "text/x-python", 565 | "name": "python", 566 | "nbconvert_exporter": "python", 567 | "pygments_lexer": "ipython3", 568 | "version": "3.6.1" 569 | } 570 | }, 571 | "nbformat": 4, 572 | "nbformat_minor": 2 573 | } 574 | -------------------------------------------------------------------------------- /Reader/lecture9_conclusion.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Conclusion\n", 8 | "\n", 9 | "In the last few chapters we have discussed how to extend our toolbox for expressing abstractions and abstract concepts via programming languages by means of functions.\n", 10 | "\n", 11 | "We have begun by showing functions as a way to capture dynamic aspects of reality (_actions_), and to define custom instructions. Of course our primitive initial definition of functions was quite leaky: we introduced the stack, and then the ability to `return` values, in order to reduce this leakiness and ensure that the different contexts in which our program could find itself evaluating would be well separated from each other.\n", 12 | "\n", 13 | "We then started exploring the potential of our new construction. We began by defining functions that invoke themselves in their own definition, thereby building self-nesting _recursive_ functions which express a lot of even complex concepts with paradoxically little code. \n", 14 | "\n", 15 | "We then moved on to the definition of functions which take as parameters, and return as result, other functions. These higher order functions that manipulate other functions as they would any other value allow us to build highly generic constructs that embody abstract notions which must be further specified with concrete actions in order to be concretely usable. Examples of such constructs are sequentialization of functions (`then`) and repetition of functions (`repeat`).\n", 16 | "\n", 17 | "Armed with these constructs we have produced some mind-bending representations of figures, our running example, thereby also showing that these constructs do indeed (easily) scale beyond the most trivial samples.\n", 18 | "\n", 19 | "Finally, we have shown that functions can be used to implement some advanced data representations, such as figures. With higher order combinators it then becomes possible to define (algebra's of) operators on the represented data, such as ways to unite, invert, intersect, etc. existing figures.\n", 20 | "\n", 21 | "Thanks to functions we will keep building our ladder of abstractions, starting with objects and classes in the next part of the text." 22 | ] 23 | } 24 | ], 25 | "metadata": { 26 | "kernelspec": { 27 | "display_name": "Python 3", 28 | "language": "python", 29 | "name": "python3" 30 | }, 31 | "language_info": { 32 | "codemirror_mode": { 33 | "name": "ipython", 34 | "version": 3 35 | }, 36 | "file_extension": ".py", 37 | "mimetype": "text/x-python", 38 | "name": "python", 39 | "nbconvert_exporter": "python", 40 | "pygments_lexer": "ipython3", 41 | "version": "3.6.1" 42 | } 43 | }, 44 | "nbformat": 4, 45 | "nbformat_minor": 2 46 | } 47 | -------------------------------------------------------------------------------- /modulewijzer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# INFDEV02-2 (Development 2, 2017-18)" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Introduction\n", 15 | "This is the course descriptor for the _Development 2_ course.\n", 16 | "\n", 17 | "Development 2 covers the concepts in and around functional abstraction. The course opens with a description of the mechanisms of abstraction and its relationship to code reuse. It then moves on to a discussion about the semantics of function call. The stack is shown as a parameter storage, which plays a particularly interesting role when functions call other functions. Recursion is then introduced as a mechanism for calling a function from within its own body. Finally, we will introduce higher order functions and anonymous (lambda) functions. We will ultimately also combine all of these concepts (recursion, higher order functions, and anonymous functions) in large architectures based on some generic code. \n", 18 | "Many concrete examples are shown throughout the whole course: given the abstract nature of the concepts, we will strive towards showing many instances of these topics in action. The examples are small in size, but complex and exemplary in nature.\n", 19 | "\n", 20 | "An important reminder: the course (and the whole Informatica degree of Hogeschool Rotterdam as well, for that matter) is not meant to provide students with a series of tips and tricks to be quickly successful at one's job. Rather, the course aims to build a solid foundation upon which learning of all sorts of programming languages will be based. This *will*, without a doubt, prepare students not only for their first professional tasks, but for all their evolutions. Moreover, understanding of these concepts will allow students to wield their professional tools (programming languages) with certainty instead of intuition, thereby greatly increasing the quality of what they deliver and their value on the job market." 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "### Learning goals\n", 28 | "The course has the following learning goals:\n", 29 | "- (ICODE) students can _recognize and formally interpret_ components of a programming language in their formal semantics: function definition, stack, function call, anonymous function, recursion, higher order function;\n", 30 | "- (ECODE) students _understand code_; _understand_ here means that students can describe the effect of running the program by predicting the sequence of state transitions produced by its execution;\n", 31 | "- (WCODE) students can _write small snippets of code_ in order to complete an existing program, from a clearly given specification of the state transitions that the completed program is expected to perform.\n", 32 | "\n", 33 | "The course, and therefore also the learning goals, are limited to imperative programs featuring a pervasive functional core, and the following Python 3 keywords and operators: \n", 34 | "- `def`\n", 35 | "- `return`\n", 36 | "- `lambda`\n", 37 | "\n", 38 | "All concepts and keywords learnt during the previous development courses (_Development 1_) are assumed to be available and built upon.\n", 39 | "\n", 40 | "The corresponding competences connected to these learning goals are: \n", 41 | "- realisation.\n", 42 | "\n", 43 | "### Learning materials\n", 44 | "\n", 45 | "The mandatory learning materials are:\n", 46 | "- reader of the lectures, which will be made available one week after the lecture\n", 47 | "- materials on the Github repository (github.com/hogeschool), which is already online" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "### Exam\n", 55 | "The exam is divided in two parts:\n", 56 | "#### Theory\n", 57 | "- (FA) a series of four (4) *forward assignments*, that is exercises where students, given full code and partial state transitions produced by the code, are requested to complete the missing state transitions the code will produce;\n", 58 | "- (MC) a series of eight (8) *multiple choice* questions on the formal interpretation of state transitions induced by statements, expression evaluation, and state manipulation.\n", 59 | "\n", 60 | "#### Practice\n", 61 | "- (BA) a series of five (5) *backward assignments*, that is exercises where students, given partial code and the desired state transitions, are requested to fill in the missing code that matches the given state transitions.\n", 62 | "\n", 63 | "#### Scoring\n", 64 | "The theory results in a grade which is either PASS or FAIL. Each question awards one single point, if correctly answered. Students who score at least 75% of the total points will get a passing grade (PASS). For example, if the theory exam is made up by 12 questions in total, then a student needs to answer correctly at least 9 questions (since 8/12 = 67% and 9/12 = 75%) to get a PASS.\n", 65 | "\n", 66 | "Here below you can see some example of results. In the first column you find the percentage of correct answers given by the student. In the second column there is the corresponding grade in a scale from 0 to 10 that you will find in Osiris. The final result of this part of the exam can be seen in the third column: PASS (when the points percentage is greater than or equal to 75) or FAIL (otherwise).\n", 67 | "\n", 68 | "| Points percentage | Corresponding grade (from 0 to 10) | Result of the theory exam |\n", 69 | "| ----------------- |:------------------:|:-------------:|\n", 70 | "| 0% | 0 | FAIL |\n", 71 | "| 12.5% | 0.9 | FAIL |\n", 72 | "| 25.0% | 1.8 | FAIL |\n", 73 | "| 37.5% | 2.8 | FAIL |\n", 74 | "| 50.0% | 3.7 | FAIL |\n", 75 | "| 62.5% | 4.6 | FAIL |\n", 76 | "| 75.0% | 5.5 | PASS |\n", 77 | "| 87.5% | 7.8 | PASS |\n", 78 | "| 100.0% | 10 | PASS |\n", 79 | "\n", 80 | "\n", 81 | "The practice results in a full grade (from 0 to 10). Each assignment awards one single point, if correctly completed. The grade is computed as the percentage of points obtained, divided by 10. Students who score at least 55% of the total points will thus get a passing grade. For example, if a student completes 3 assignments out of 5, this means obtaining 3 points out of 5, which means 60% and corresponds thus to a 6 (60/10).\n", 82 | "\n", 83 | "**You receive the credit points for this course as soon as the theory result is PASS and the practice result is greater than or equal to 5.5**.\n", 84 | "\n", 85 | "Notice that, even though there are more FA's and MC's than BA's, the BA's require actively _writing code_, which is significantly more complex than reading code and describing states and bindings, seen that it combines technical understanding and creativity.\n", 86 | "\n", 87 | "#### Retake\n", 88 | "If only one of the exam parts is passed (theory or practice), then only the other will need to be retaken.\n", 89 | "\n", 90 | "#### Exam matrix\n", 91 | "The exam covers all learning goals.\n", 92 | "\n", 93 | "| Exam part | ICODE | ECODE | WCODE |\n", 94 | "| ------------- |:-------------:|:-------------:|:-------------:|\n", 95 | "| FA | | V | |\n", 96 | "| MC | V | | |\n", 97 | "| BA | | | V |\n" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "## Lecture plan\n", 105 | "The course is made up of eight lectures and practicums, usually planned as one lecture and one practicum per lesson week (for details, see lesson schedule on HINT). \n", 106 | "The lectures begin with an introduction of the basic concepts, which are then elaborated during the practicums. The lectures handle the theory, plus applied examples. Some of the applied examples are then further elaborated during the practicums.\n", 107 | "\n", 108 | "The lesson units covered by the course are the following:\n", 109 | "- Abstract vs concrete\n", 110 | "- Functional abstraction\n", 111 | "- Function definition and call\n", 112 | "- The stack\n", 113 | "- Functions calling other functions\n", 114 | "- Recursion and the induction principle\n", 115 | "- Higher order functions\n", 116 | "- Anonymous functions\n", 117 | "- An example applied to drawing: a higher order raster drawing function and an algebra of predicates \n", 118 | "\n", 119 | "Note that each lesson unit does not necessarily correspond to one lesson week (for example, one lesson unit could span during two lesson weeks or two lesson units could be handled during one lesson week)." 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "## Study points and contact time\n", 127 | "The course awards students 4 ECTS, in correspondence with 112 hours of study.\n", 128 | "\n", 129 | "The course consists of eight frontal lectures for the theory, and eight assisted practicums. The rest is self study." 130 | ] 131 | } 132 | ], 133 | "metadata": { 134 | "kernelspec": { 135 | "display_name": "Python 3", 136 | "language": "python", 137 | "name": "python3" 138 | }, 139 | "language_info": { 140 | "codemirror_mode": { 141 | "name": "ipython", 142 | "version": 3 143 | }, 144 | "file_extension": ".py", 145 | "mimetype": "text/x-python", 146 | "name": "python", 147 | "nbconvert_exporter": "python", 148 | "pygments_lexer": "ipython3", 149 | "version": "3.5.2" 150 | } 151 | }, 152 | "nbformat": 4, 153 | "nbformat_minor": 1 154 | } 155 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | This is the repository for the course Development 1 course at Hogeschool Rotterdam. 3 | 4 | Please head to the [modulewijzer](modulewijzer.ipynb) in order to get basic information about the course, or head to the [lessons](Lectures) for a detailed overview of all lectures. 5 | 6 | 7 | ## Setup of Jupyter 8 | The various content files can be seen directly via GitHub. 9 | 10 | In order to be able to open and preview (or even edit) the files locally, on your machine, you will need to [install Jupyter Notebook](https://jupyter.readthedocs.io/en/latest/install.html). 11 | 12 | After Jupyter Notebook is installed, then just run `jupyter notebook` on your command line and navigate to the right files. 13 | 14 | ### Note on Windows 15 | Jupyter Notebook works eccellently under [bash on Windows](https://msdn.microsoft.com/en-us/commandline/wsl/about), and is quite easy to install. 16 | --------------------------------------------------------------------------------