Blog of Stuff: Posts tagged 'Teaching'urn:http-www-brinckerhoff-org:-tags-Teaching-html2020-12-11T13:46:35ZOn the relationship between mathematical functions and program functionsurn:http-www-brinckerhoff-org:-blog-2020-12-11-on-the-relationship-between-mathematical-functions-and-program-functions2020-12-11T13:46:35Z2020-12-11T13:46:35ZJohn Clements
<p>NOTE: this text is a brief overview intended for instructors of a CS course that we’re developing; it is not technical. It tries to make the case that our early courses should steer students toward understanding problems in terms of pure functions. If you have suggestions or feedback, I’d love to hear them/it.</p>
<hr />
<p>This section of the course introduces functions, a crucial topic in the field of computer science AND in the field of math.</p>
<p>Programmers and mathematicians sometimes think about the term “function” somewhat differently. Furthermore, some people who are familiar with both fields assign different meanings to the word “function” in the two fields.</p>
<p>The definition of a function in the mathematical domain is fairly well specified, though of course things get a little fuzzy around the edges. We’re going to define functions as the arrows in the category Set, more or less (if that’s not helpful, ignore it). That is, a function has a specified domain and a specified codomain, and it maps every element of the domain to a particular element in the codomain. There’s no requirement that it map every element of the domain to a different element of the codomain (one-to-one) OR that there be some element of the domain that maps to any chosen element of the codomain (onto). This (I claim) is the standard notion of “function” in math.[*]</p>
<p>Programmers also use and love functions. Nearly every programming language has a notion of functions. Of course, they’re sometimes called “procedures” or even “paragraphs” (I believe that’s COBOL. Yikes.). In programming, functions are often thought of as being elements of abstraction that are designed to allow repetition. And so they are. But it turns out that they also, in most modern programming languages, can be thought of as mathematical functions. Well, some of them can.</p>
<p>For many functions, this is totally obvious. If I consider the function from numbers to numbers that we might write in math class as f(x) = 14x + 2, then I can write that as a function in most programming languages. (If you disagree with me, hold that thought.)</p>
<p>But… things aren’t always so clear. What about a function that doesn’t return at all? What about a function that takes input, or produces output? What about a function that mutates an external variable, or reads a value from a mutable value? What about a function that signals an error? All of these present problems, some more substantial than others. None of these have a totally obvious mapping to mathematical functions.</p>
<p>There certainly <em>are</em> ways to fit these functions into mathematical models, but in general, the clearest lesson is that when there <em>is</em> a natural way to express a problem using functions that map directly to mathematical functions, we should. These are generally called “pure” or “purely functional” functions.</p>
<p>So, why should it matter whether our functions are pure? What benefits do we gain when we express functions in purely functional ways?</p>
<p>The clearest one is predictability, also known as debuggability and testability. When I write a pure function that maps the input “savage speeders” to 17, then I know that it will <em>always</em> map that string to 17; I don’t need to worry that it will work differently when the global foo counter is less than zero, or when it’s run in parallel, or on Wednesday, or when the value in memory location 0x3342a7 is less than the value in memory location 0x3342a8.</p>
<p>Put differently, pure functions allow me to reliably decompose problems into sub-pieces. When I’m debugging and testing, I don’t need to worry about setup and teardown to establish external conditions.</p>
<p>Another way to understand this is to dip into functions that use mutation. If we want to model these as mathematical functions, we need to understand that in addition to their stated inputs, they take additional hidden inputs. In the simplest case, this may be the value of a global counter. Things get much more complex when we allow mutation of data structures; now we need to worry about whether two values are the “same” value; that is, whether mutating one of them will change the other. Worse still, mutating certain values may affect the evaluation of other concurrent procedures.</p>
<p>For these reasons and others like them, pure functions are vastly easier to reason about, debug, and maintain. Over time, many of our programming domains and paradigms are migrating toward primarily-pure settings. Examples include the spread of the popular map-reduce frameworks, and the wild explosion of popularity in deep learning networks. In both cases, the purity spreads downward from a mathematical framework.</p>
<p>Note that it is <em>not</em> always the case that pure approaches are the most natural “first choice” for programmers, especially introductory programmers, for whom programs are often imagined as a “sequence of changes”; do this, then do this, then do this, then you’re done. In this model, the program is performing a series of mutations on a larger world. Helping introductory programmers move to a purer model is a challenge, but one with substantial payoff.</p>
<p>For this reason, this section focuses directly on pure functions, and invites students to conceive of programs using the models that they’ve been taught in elementary and secondary school, most particularly tables mapping inputs to outputs.</p>
<p>[*] The only reason I mention the category Set is to draw attention to the distinction between “codomain” and “range”; every function has a named codomain, regardless of whether its range covers it. For instance, the “times-two” function from Reals to Reals is a different function from the “times-two” function from integers to integers, and the “times-two” function from integers to reals is yet a third function.</p>knowing what's out thereurn:http-www-brinckerhoff-org:-blog-2017-10-03-knowing-what-s-out-there2017-10-03T15:23:23Z2017-10-03T15:23:23ZJohn Clements
<p>I’m teaching a class to first-year college students. I just had a quick catch-up session with some of the students that had no prior programming experience, and one of them asked a fantastic question: “How do you know what library functions are available?”</p>
<p>In a classroom setting, teachers can work to prevent this kind of question by ensuring that students have seen all of the functions that they will need, or at least that they’ve seen enough library functions to complete the assignment.</p>
<p>But what about when they’re trying to be creative, and do something that might or might not be possible?</p>
<p>Let’s take a concrete example: a student in a music programming question wants to reverse a sound. How can this be done?</p>
<!-- more-->
<p>First thing, they probably look in the documentation for the sound library. They scan the list of function names, and none of them looks like something that reverses sounds. Should they give up?</p>
<p>Answer: no.</p>
<p>So what should they do?</p>
<p>Probably, they should look for a more general function that could be used to accomplish this task. For a first year student, this is a deeply daunting task. That is, they have to look at a sequence of thirty or forty functions and for each one, quickly determine whether or not it could be used in a program to accomplish the desired task. Even worse, it may be the case that they need to assemble several of these functions. In this case, the student needs to look at a function and think, “Gee… this function could be part of a solution, if I can figure out a way to solve this new problem that it creates.” You can see that this can lead to an exponential exploration of problem space.</p>
<p>Insted, an experienced programmer will probably tacitly generalize the desired function. “There’s no function that reverses a sound; is there any kind of more general sound rearranging that’s possible?” The answer here is yes. Unfortunately, there are actually two different ways to do this. First, there’s a <code>clip</code> function that can cut a portion out of a sound, and there’s an <code>append</code> operation that can glue sounds together. Separately, there’s a <code>rearrange</code> function that allows a user to provide a function to be used in mapping the frames of an old sound onto a new one.</p>
<p>Which one to use? It turns out that the first solution has some serious computational issues associated with it. If I want to reverse a three-minute sound, I’ll need to create a list containing 8,640,000 separate sounds.<sup><a href="#2017-10-03-knowing-what-s-out-there-footnote-1-definition" name="2017-10-03-knowing-what-s-out-there-footnote-1-return">1</a></sup> This will probably take a while. The second one is much faster. So now, even after finding a set of library functions that work, the student needs to backtrack and try a different one.</p>
<p>Ouch.</p>
<h3 id="fast-forward-one-day">Fast Forward One Day</h3>
<p>Okay, so now it’s Tuesday.</p>
<p>I’m working on the handin server for my upper-division Programming Languages class, and I notice a funny problem in the logs. To wit:</p>
<pre><code>[33|2017-10-03T11:05:03] checking Program2 for (clements)
[33|2017-10-03T11:05:04] running 794KB (170MB 184MB)
[33|2017-10-03T11:05:09] running 70MB (239MB 255MB)
[33|2017-10-03T11:05:09] done testing. 0 tests failed.
[33|2017-10-03T11:05:13] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:17] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:21] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:25] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:29] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:33] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:36] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:40] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:44] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:48] running 54MB (224MB 238MB)
[33|2017-10-03T11:05:51] session killed (timeout) while running tests</code></pre>
<p>What we see here is that at 11:05:09, the submission passed all the tests. However, at 11:05:51, the server timed out and the submission was marked as a failure.</p>
<p>Why?</p>
<p>The TL;DR here is this: I didn’t know (enough) about a library function. But it took me an hour to figure it out.</p>
<p>Here’s the extended version: I took a look at my checker module, and it looked fine. Specifically, it ended by logging the number of failures, and then showing a dialog box to the user that indicated the number of failures. Nothing wrong there.</p>
<p>I spent about ten minutes writing a careful e-mail to the Racket Users mailing list, trying to figure out what’s wrong with the handin server. As I was carefully explaining why it couldn’t be my fault… I realized that it was. Specifically, my dialog box was a modal one, waiting for the user to click ‘OK’. If the user doesn’t click ‘OK’, the call to the message box function doesn’t return, and the checker module doesn’t finish, and as far as the handin server is concerned, the user’s program has failed to halt and should be removed. Ouch. Delete e-mail before sending.</p>
<p>Side note: if you don’t use the unbelievably effective technique of carefully formulating a bug report for a group of people that you respect and whose time you’re leery of wasting—you should. It works.</p>
<p>Sub-side note: unfortunately, if you’re in college, you probably don’t have the judgment or patience to be aware that you’re wasting other people’s time. Ah well.</p>
<p>So, how to solve this problem? My first try is to put the call to <code>message</code> in its own thread.</p>
<p>I’m not entirely surprised when this starts behaving very badly indeed; I get messages like</p>
<pre><code>[1|2017-10-03T11:10:20] ERROR: upload not confirmed: hekok</code></pre>
<p>and</p>
<pre><code>[2|2017-10-03T11:11:13] ERROR: upload not confirmed: chc</code></pre>
<p>What the heck??</p>
<p>After a quick search for the string ‘hekok’ in the source, I decide that that’s probably a different scary bug, and that I don’t have time to debug it.</p>
<p>Side note: It makes me sad to see what I think might be bugs that I decide not to pursue; it’s a chance to make the world slightly better that I’m deliberately passing up. Of course, it might take a long time to track them down, and in many cases, they turn out not to be bugs at all.</p>
<p>So, maybe I can make this message part of a final result. Time to read some docs: I read the docs for the <code>check:</code> form that defines the checker module, and all of the optional arguments. Nothing. Then, on a whim, I decide to read the docs for the <code>message</code> form.</p>
<p>Aha! It turns out that the <code>message</code> form can be passed the style <code>final</code>, in which case the message is used as the final message to the student, <em>after</em> the submission is complete.</p>
<p>This is exactly what I want, and I use it.</p>
<p>Problem solved.</p>
<p>It’s at about this moment that I realize that what I’ve been experiencing is almost exactly the same problem that my students are facing; I don’t know what’s in the libraries. In some ways, I’m even more dangerous than the students, because I know of ways to hack around the problem, and solve it the wrong way.</p>
<p>So: how are we supposed to know what libraries are available? In my mind, it’s still a major open question. Let’s see if I can get anyone at RacketCon interested.</p>
<h3 id="alternate-ending">Alternate Ending</h3>
<p>There’s an alternate, depressing conclusion to my experience: it’s incredibly hard to get software right. Every piece of software is riddled with problems like this that don’t occur frequently, and are arguably not even “bugs”, per se, except that they obviously are, and fixing one takes about an hour of skilled programmer time. There’s not enough time in the day. It’s all going to fall apart.</p>
<p>Or maybe we’re going to give up on “engineering” our programs and fall back to “evolving” them; we accept an ecosystem of horribly buggy software and choose the best stuff and apply weird patches and lash them together with baling wire. Nasty, but it really works.</p>
<p><sup><a href="#2017-10-03-knowing-what-s-out-there-footnote-1-definition" name="2017-10-03-knowing-what-s-out-there-footnote-1-return">1</a></sup> : … or 100x the number of seconds in a day. Coincidence, sorry.</p>
<div class="footnotes">
<ol></ol></div>ontologies OF programsurn:http-www-brinckerhoff-org:-blog-2017-08-22-ontologies-of-programs2017-08-22T11:59:33Z2017-08-22T11:59:33ZJohn Clements
<p>Reading Daniel Dennett’s “From Bacteria to Bach and Back” this morning, I came across an interesting section where he extends the notion of ontology—a “system of things that can be known”—to programs. Specifically, he writes about what kinds of things a GPS program might know about: latitudes, longitudes, etc.</p>
<p>I was struck by the connection to the “data definition” part of the design recipe. Specifically, would it help beginning programmers to think about “the kinds of data that their program ‘knows about’”? This personification of programs can be seen as anti-analytical, but it might help students a lot.</p>
<p>Perhaps I’ll try it out this fall and see how it goes.</p>
<p>Okay, that’s all.</p>restrictive or: notes from the dark sideurn:http-www-brinckerhoff-org:-blog-2017-04-26-restrictive-or-notes-from-the-dark-side2017-04-26T14:33:13Z2017-04-26T14:33:13ZJohn Clements
<p>Okay, it’s week four of data structures in Python. In the past few days, I’ve read a lot of terrible code. Here’s a <em>beautiful</em>, <em>horrible</em>, example:</p>
<div class="brush: Python">
<div class="source">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre><span class="normal"> 1</span>
<span class="normal"> 2</span>
<span class="normal"> 3</span>
<span class="normal"> 4</span>
<span class="normal"> 5</span>
<span class="normal"> 6</span>
<span class="normal"> 7</span>
<span class="normal"> 8</span>
<span class="normal"> 9</span>
<span class="normal">10</span>
<span class="normal">11</span>
<span class="normal">12</span>
<span class="normal">13</span>
<span class="normal">14</span>
<span class="normal">15</span>
<span class="normal">16</span>
<span class="normal">17</span>
<span class="normal">18</span>
<span class="normal">19</span>
<span class="normal">20</span>
<span class="normal">21</span>
<span class="normal">22</span>
<span class="normal">23</span>
<span class="normal">24</span>
<span class="normal">25</span></pre></div></td>
<td class="code">
<div>
<pre><span></span><span class="c1"># An IntList is one of</span>
<span class="c1"># - None, or</span>
<span class="c1"># - Pair(int, IntList)</span>
<span class="k">class</span> <span class="nc">Pair</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">first</span><span class="p">,</span> <span class="n">rest</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">first</span> <span class="o">=</span> <span class="n">first</span>
<span class="bp">self</span><span class="o">.</span><span class="n">rest</span> <span class="o">=</span> <span class="n">rest</span>
<span class="c1"># standard definitions of __eq__ and __repr__ ...</span>
<span class="c1"># A Position is one of</span>
<span class="c1"># - an int, representing a list index, or</span>
<span class="c1"># - None</span>
<span class="c1"># IntList int -> Position</span>
<span class="c1"># find the position of the sought element in the list, return None if not found.</span>
<span class="k">def</span> <span class="nf">search</span><span class="p">(</span><span class="n">l</span><span class="p">,</span> <span class="n">sought</span><span class="p">):</span>
<span class="k">if</span> <span class="n">l</span> <span class="o">==</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">None</span>
<span class="n">rest_result</span> <span class="o">=</span> <span class="n">search</span><span class="p">(</span><span class="n">l</span><span class="o">.</span><span class="n">rest</span><span class="p">,</span> <span class="n">sought</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">l</span><span class="o">.</span><span class="n">first</span> <span class="o">==</span> <span class="n">sought</span> <span class="ow">or</span> <span class="n">rest_result</span><span class="p">)</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">l</span><span class="o">.</span><span class="n">first</span> <span class="o">==</span> <span class="n">sought</span><span class="p">:</span>
<span class="k">return</span> <span class="mi">0</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="mi">1</span> <span class="o">+</span> <span class="n">rest_result</span>
</pre></div></td></tr></tbody></table></div>
</div>
<p>This code <em>works correctly</em>. It searches a list to find the position of a given element. Notice anything interesting about it?</p>
<p>Take a look at the parentheses in the <code>if</code> line. How do you feel about this code now?</p>
<p>(Spoilers after the jump. Figure out why it works before clicking, and how to avoid this problem.)</p>
<!-- more-->
<p>Okay, that’s just awful. The intent was to write</p>
<div class="brush: Python">
<div class="source">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre><span class="normal">1</span></pre></div></td>
<td class="code">
<div>
<pre><span></span><span class="k">if</span> <span class="p">(</span><span class="n">l</span><span class="o">.</span><span class="n">first</span> <span class="o">==</span> <span class="n">sought</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">rest_result</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">):</span>
</pre></div></td></tr></tbody></table></div>
</div>
<p>instead of</p>
<div class="brush: python">
<div class="source">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre><span class="normal">1</span></pre></div></td>
<td class="code">
<div>
<pre><span></span><span class="k">if</span> <span class="p">(</span><span class="n">l</span><span class="o">.</span><span class="n">first</span> <span class="o">==</span> <span class="n">sought</span> <span class="ow">or</span> <span class="n">rest_result</span><span class="p">)</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
</pre></div></td></tr></tbody></table></div>
</div>
<p>… but the student misparenthesized. The student’s code works because if <code>l.first</code> is equal to <code>sought</code>, the <code>or</code> evaluates to <code>True</code>, which is in fact not equal to <code>None</code>, so you wind up in the right place. Otherwise, the <code>or</code> winds up being the value of rest_result, which is then correctly compared to <code>None</code>. Note also that there’s no <code>else</code> case, meaning that the function secretly returns <code>None</code> in the fall-through, which is also correct.</p>
<p>I hope you agree with me that this code is abominable, and we’d like to prevent students from writing it.</p>
<p>What’s the fix? I think the obvious fix is to ensure that <code>or</code> only works on booleans. This is guaranteed by most typed languages in the type system, or by a dynamic check in the implementation of <code>or</code>.</p>
<p>Does Racket have this problem? No, it does not. In the case of Racket, though, it’s because of the notion of “student languages,” an idea to which many people pay lip service but which few people actually carry out.</p>
<p>Anyway: types or language levels FTW. Or, more precisely:</p>
<p>Python FTL!</p>Not liking Python any better nowurn:http-www-brinckerhoff-org:-blog-2017-03-11-not-liking-python-any-better-now2017-03-12T03:33:25Z2017-03-12T03:33:25ZJohn Clements
<p>It’s much closer to ‘go’ time now with Python, and I must say, getting to know Python better is <em>not</em> making me like it better. I know it’s widely used, but it really has many nasty bits, <em>especially</em> when I look toward using it for teaching. Here’s my old list:</p>
<ul>
<li>Testing framework involves hideous boilerplate.</li>
<li>Testing framework has standard problems with floating-point numbers.</li>
<li>Scoping was clearly designed by someone who’d never taken (or failed to pay attention in) a programming languages course.</li>
<li>The vile ‘return’ appears everywhere.</li></ul>
<p>But wait, now I have many more, and I’m a bit more shouty:</p>
<ul>
<li>Oh dear lord, I’m going to have to force my students to implement their own equality method in order to get test-case-intensional checking. Awful. Discovering this was the moment when I switched from actually writing Python to writing Racket code that generates Python. Bleah.</li>
<li>Python’s timing mechanism involves a hideously unhygienic “pass me a string representing a program” mechanism. Totally dreadful. Worse than C macros.</li>
<li>Finally, I just finished reading <a href="http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html">Guido Van Rossum’s piece on tail-calling</a>, and I find his arguments not just unconvincing, not just wrong, but sort of deliberately insulting. His best point is his first: TRE (or TCO or just proper tail-calling) <em>can</em> reduce the utility of stack traces. However, the solution of translating this code to loops destroys the stack traces too! You can argue that you lose stack frames in those instances in which you make tail calls that are not representable as loops, and in that case I guess I’d point you to our <a href="http://dl.acm.org/citation.cfm?id=1034778">work with continuation marks</a>. His next point can be paraphrased as “If we give them nice things, they might come to depend on them.” Well, yes. His third point suggests to me that he’s tired of losing arguments with Scheme programmers. Fourth, and maybe this is the most persuasive, he points out that Python is a poorly designed language and that it’s not easy for a compiler to reliably determine whether a call is in tail position. Actually, it looks like he’s wrong even here; I read it more carefully, and he’s getting hung up on some extremely simple scoping issues. I’m really not impressed by GvR as a language designer.</li></ul>time spent on CPE430, Spring 2016urn:http-www-brinckerhoff-org:-blog-2016-06-08-time-spent-on-cpe430-spring-20162016-06-08T13:31:05Z2016-06-08T13:31:05ZJohn Clements
<p>Earlier this year, I was talking to Kurt Mammen, who’s teaching 357, and he mentioned that he’d surveyed his students to see how much time they were putting into the course. I think that’s an excellent idea, so I did it too.</p>
<p>Specifically, I conducted a quick end-of-course survey in CPE 430, asking students to estimate the number of weekly hours they spent on the class, outside of lab and lecture.</p>
<p>Here are some pictures of the results. For students that specified a range, I simply took the mean of the endpoints of the range as their response.</p>
<div class="figure"><img src="/img/cpe430/cpe430-2164-time-density.png" alt="Density of responses" />
<p class="caption">Density of responses</p></div>
<p>Then, for those who will complain that a simple histogram is easier to read, a simple histogram of rounded-to-the-nearest-hour responses:</p>
<div class="figure"><img src="/img/cpe430/cpe430-2164-time-histogram.png" alt="Histogram of responses" />
<p class="caption">Histogram of responses</p></div>
<p>Finally, in an attempt to squish the results into something more accurately describable as a parameterizable normal curve, I plotted the density of the natural log of the responses. Here it is:</p>
<div class="figure"><img src="/img/cpe430/cpe430-2164-log-time-density.png" alt="Density of logs of responses" />
<p class="caption">Density of logs of responses</p></div>
<p>Sure enough, it looks much more normal, with no fat tail to the right. This may just be data hacking, of course. For what it’s worth, the mean of this curve is 2.13, with a standard deviation of 0.49.</p>
<p>(All graphs generated with <a href="https://www.racket-lang.org">Racket</a>.)</p>things I already dislike about Pythonurn:http-www-brinckerhoff-org:-blog-2016-05-16-things-i-already-dislike-about-python2016-05-16T16:55:56Z2016-05-16T16:55:56ZJohn Clements
<p>I’m just getting started, but already Python is looking like a terrible teaching language, relative to Racket.</p>
<ul>
<li>Testing framework involves hideous boilerplate.</li>
<li>Testing framework has standard problems with floating-point numbers.</li>
<li>Scoping was clearly designed by someone who’d never taken (or failed to pay attention in) a programming languages course.</li>
<li>The vile ‘return’ appears everywhere.</li></ul>Break it! Confrontational thinking in computer scienceurn:http-www-brinckerhoff-org:-blog-2015-06-14-break-it-confrontational-thinking-in-computer-science2015-06-14T19:09:51Z2015-06-14T19:09:51ZJohn Clements
<p>So here I am grading another exam. This exam question asks students to imagine what would happen to an interpreter if environments were treated like stores. Then, it asks them to construct a program that would illustrate the difference.</p>
<p>They fail, completely.</p>
<p>(Okay, not completely.)</p>
<p>By and large, it’s pretty easy to characterize the basic failing: these students are unwilling to break the rules.</p>
<!-- more-->
<p>I feel like this idea has been hitting me over the head for a couple of months, now, so I’m going to write it down. Specifically, I see many areas of computer science where it’s important to engage in what I call “confrontational thinking.” Taking something that looks good, and poking holes in it to find out what’s wrong with it.</p>
<p>Is this what other people call “critical thinking”? I don’t think so. Associations are important, and I think that the term “critical thinking” now simply refers to a watery desire for students to somehow be more well-rounded and high-level thinkers. I’m thinking of something different.</p>
<p>For me, I think this skill is most closely affiliated with Math. Specifically, if you want to succeed in Math (and no, I don’t mean the ability to memorize and deploy the closed-form solution to quadratics), you need to be able to take a nice idea and bash it against the wall. Find counterexamples, think outside the box. Try to prove your professor wrong. All kinds of interesting things fall out when you disassemble the nice clean theorems that you’re given.</p>
<p>In programming, this may be even more important. In programming, after all, the artifacts are not theorems given to you by teachers (and proven by Euclid)—they’re programs you wrote yourself, and they’re probably jammed with bugs. If you can’t attack your program, and try to break it, you’re going to develop fragile artifacts that work only for the corner that your mind was stuck in.</p>
<p>In the classroom, this is how you learn. Students that patiently absorb all of the text on the board don’t appear (to me) to actually be learning anything; it’s those that are in your face—as long as they can clearly enunciate their questions—that are assembling knowledge.</p>
<p>I was reminded of this by watching one of Veritasium’s excellent youtube posts, this one on <a href="https://www.youtube.com/watch?v=vKA4w2O61Xo">discovering the rule that generates a sequence of numbers</a>. Excellent, I should say, except that I wanted to shout at the people in the video. Go take a look to see what I mean. These people are having a lot of trouble engaging in confrontational thinking.</p>
<p>Why do students have such trouble with this?</p>
<p>I think that one reason may be that we learn different kinds of things in different ways.</p>
<p>So, for instance, consider learning to walk. This is something we learn very early, long before we’re able to absorb complex instructions. We’re certainly partially hard-wired for this, but a big chunk of it is trial and error.</p>
<p>Here’s the point, though. Once we learn to walk, do we engage in confrontational thinking about the process? Heck no! Deliberately try to walk wrong, to see what happens? I’m pretty sure I can accurately guess what’s going to happen, and it’s probably going to involve bleeding and bandages.</p>
<p>For this kind of activity, confrontational thinking is not the best idea. Instead, we find the way that works, and we stick with it.</p>
<p>More generally, this is the way that we actually get things done in our lives. If every morning I decide to question the way that breakfast works—whether I can fry an egg without a pan, or whether the food needs to go in my mouth—I’m going to end up messy and frustrated.</p>
<p>For many of our students, I claim, programming is like this. After many painful episodes of trial and error, they’ve developed scars and bruises, and know of one narrow path that happens to work pretty well. Getting them to consider other paths is frightening and aversive. (Say, for instance, the parenthesized syntax of lisp-like languages.)</p>
<p>In fact, if you want to really go overboard, you can compare these two learning styles to the notions of conservatism and liberality. The conservative style gets the job done, using fewer resources and taking fewer risks. The liberal style involves self-doubt and failure, but is ultimately necessary in order to learn.</p>
<p>Hmm… when I put it that way, it sounds perfectly obvious.</p>
<p>Now, the only question is how to get the students to engage, and start questioning me.</p>Is teaching programming like teaching math?urn:http-www-brinckerhoff-org:-blog-2014-12-17-is-teaching-programming-like-teaching-math2014-12-17T14:13:02Z2014-12-17T14:13:02ZJohn Clements
<p>One of my children is in third grade. As part of a “back-to-school” night this year, I sat in a very small chair while a teacher explained to me the “Math Practices” identified as part of the new Common Core standards for math teaching.</p>
<p>Perhaps the small chair simply made me more receptive, taking me back to third grade myself, but as she ran down the list, I found myself thinking: “gosh, these are exactly the same skills that I want to impart to beginning programmers!”</p>
<p>Here’s the list of Math Practices, a.k.a. <a href="https://duckduckgo.com/l/?kh=-1&uddg=http%3A%2F%2Fwww.corestandards.org%2FMath%2FPractice%2F">“Standards for Mathematical Practice”</a>:</p>
<ol>
<li>Make sense of problems and persevere in solving them.</li>
<li>Reason abstractly and quantitatively.</li>
<li>Construct viable arguments and critique the reasoning of others.</li>
<li>Model with Mathematics.</li>
<li>Use appropriate tools strategically.</li>
<li>Attend to precision.</li>
<li>Look for and make use of structure.</li>
<li>Look for and express regularity in repeated reasoning.</li></ol>
<p>Holy Moley! Those are <em>incredibly</em> relevant in teaching programming. Furthermore, they sound like they were written by someone intimately familiar with the <a href="http://www.htdp.org">How To Design Programs</a> or <a href="http://www.bootstrapworld.org">Bootstrap</a> curricula. Indeed, in the remainder of my analysis, I’ll be referring specifically to the steps 1–4 of the design recipe proposed by HtDP (as, e.g., “step 2 of DR”).</p>
<p>Let’s take those apart, one by one:</p>
<!-- more-->
<ol>
<li>
<p><strong>Make sense of problems and persevere in solving them.</strong></p>
<p>This is really two things, but they’re both incredibly important in programming. The first one puts the emphasis first on understanding the problem. Don’t charge ahead and try to solve the problem (write the program) before you have some understanding of the problem. This can be clearly expressed by writing a purpose statement (part of step 2 of DR), and by designing data (step 1 of DR).</p>
<p>The second part of this—perseverance—is incredibly important. It’s not directly a step in solving the problem, but it’s one of a family of meta-skills that largely determines whether a student succeeds in an introductory programming class (erm, citation needed).</p></li>
<li>
<p><strong>Reason abstractly and quantitatively.</strong></p>
<p>Bouncing back and forth between the abstract and the quantitative is one of the key skills in programming. Indeed, a program represents a mapping from problem to solution for a large set of problems; the transition from the concrete to the abstract is the raison d’etre of programming itself.</p>
<p>Want to multiply one pair of numbers? use a calculator. Want to multiply seventy million pairs of numbers? write a program.</p>
<p>However, humans work best—and learn best—when we’re thinking about concrete, quantitative problems. That’s why step 3 of the design recipe requires students to come up with concrete examples of problem inputs, and the corresponding results. Without these concrete examples, students quickly get lost in trying to tackle all possible inputs, without being able to focus on concrete, quantitative inputs.</p></li>
<li>
<p><strong>Construct viable arguments and critique the reasoning of others.</strong></p>
<p>One of the key skills that programmers require is that of <em>understanding</em> programs. Learning to program without learning to read others’ programs is like learning to talk without knowing how to listen. It’s true that students see small examples of programs in textbooks and in class, but it’s also vital for students to see other students programs; learning to understand these will help them to see what’s missing in their own programs <sup><a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-1-definition" name="2014-12-17-is-teaching-programming-like-teaching-math-footnote-1-return">1</a></sup> <sup><a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-2-definition" name="2014-12-17-is-teaching-programming-like-teaching-math-footnote-2-return">2</a></sup> <sup><a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-3-definition" name="2014-12-17-is-teaching-programming-like-teaching-math-footnote-3-return">3</a></sup> <sup><a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-4-definition" name="2014-12-17-is-teaching-programming-like-teaching-math-footnote-4-return">4</a></sup>.</p>
<p>Also, programmers frequently engage in the activity of debugging. Okay, <em>extremely</em> frequently. The process of debugging is fundamentally one of <em>regarding one’s own program as a third party</em>. It’s clear to the programmer what they <em>meant</em> the program to do, but debugging it requires them to look at the program as if it were written by someone else, doing their best to peel off the lens of intention, and see what it actually, does, not what they meant it to do. They must then construct viable arguments as to why the program performs as it does, and how to correct it.</p>
<p>Debugging is a truly vital programming skill that is often not well taught.</p></li>
<li>
<p><strong>Model with Mathematics.</strong></p>
<p>“Model with Mathematics” is, more or less, another name for programming.</p>
<p>It’s not clear whether this adds anything to the conversation, but it seems clear that there’s more or less one hundred percent overlap between this “practice” and that of programming.</p></li>
<li>
<p><strong>Use appropriate tools strategically.</strong></p>
<p>Writing a program consists entirely in applying various functions and language constructs to a set of inputs.</p>
<p>In the first few weeks, these tools consist almost entirely of basic mathematical and graphical functions: plus, times, rectangle, and the like.</p>
<p>Later, students can bring to bear their knowledge of the “tools” of program templates (step 4 in the design recipe) to organize programs that operate on more complex forms of data. This is still in the “hand-holding” phase of programming.</p>
<p>Still later, students will learn about more sophisticated programming “tools”—divide and conquer forms of generative recursion, standard iteration functions (map, filter, foldl), and optimization techniques such as memoization. These tools pop instantly to the mind of a seasoned programmer, just as a woodworker might immediately identify the rabbet plane that will create the desired shape without difficulty.</p></li>
<li>
<p><strong>Attend to precision.</strong></p>
<p>Precision arrives somewhat later for programmers than it does for students of math. In the first ten or twenty weeks of student programming, programs tend to be entirely right or catastrophically wrong, especially if they’re following the steps of the design recipe, and using data definitions supplied to them.</p>
<p>Later, though, precision takes on an increasing importance. Programming is largely algebraic—how to combine operators and language forms to build a program—and the “precision” that is most often missing is that of corner cases, and unexpected combinations of data. “Attending to precision” in these cases consists in developing test cases that carefully cover the space of possible inputs.</p></li>
<li>
<p><strong>Look for and make use of structure.</strong></p>
<p>Making use of structure is in some ways the fundamental job of a programmer. The programmer must—before even beginning to write the program—decide how to model the data of the problem as values in some programming language. This is step 1 of the design recipe, and for the first five or six weeks of programming, students can’t be expected to design their own data.</p>
<p>In the next five or six weeks, students develop the ability to choose simple structures to represent well-understood data.</p>
<p>Finally, students move on to tackling problems where there is no single best way to model the data. In these cases, the best model may depend on operational constraints, or data volume, or any number of other criteria. Indeed, the entire fields of databases and data science may be considered to be an expression of this practice.</p></li>
<li>
<p><strong>Look for and express regularity in repeated reasoning.</strong></p>
<p>Yeah, that’s abstraction. Important in programming.</p></li></ol>
<hr />
<p>Okay, so that’s it. I hope it’s clear at this point that</p>
<p>Teaching math is a lot like teaching programming.</p>
<p>For more, take a look at Felleisen & Krishnamurthi, “Why Computer Science Doesn’t Matter,” <sup><a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-5-definition" name="2014-12-17-is-teaching-programming-like-teaching-math-footnote-5-return">5</a></sup>.</p>
<div class="footnotes">
<ol>
<li id="2014-12-17-is-teaching-programming-like-teaching-math-footnote-1-definition" class="footnote-definition">
<p>Kulkarni, Chinmay, Steven P. Dow, and Scott R. Klemmer. “Early and repeated exposure to examples improves creative work.” Design Thinking Research. Springer International Publishing, 2014. 49–62. <a href="http://link.springer.com/chapter/10.1007/978-3-319-01303-9_4">http://link.springer.com/chapter/10.1007/978–3–319–01303–9_4</a> <a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-1-return">↩</a></p></li>
<li id="2014-12-17-is-teaching-programming-like-teaching-math-footnote-2-definition" class="footnote-definition">
<p>Politz, Joe Gibbs, Shriram Krishnamurthi, and Kathi Fisler. “In-flow peer-review of tests in test-first programming.” Proceedings of the tenth annual conference on International computing education research. ACM, 2014. <a href="http://dl.acm.org/citation.cfm?id=2632347">http://dl.acm.org/citation.cfm?id=2632347</a> <a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-2-return">↩</a></p></li>
<li id="2014-12-17-is-teaching-programming-like-teaching-math-footnote-3-definition" class="footnote-definition">
<p>Hundhausen, Christopher D., Anukrati Agrawal, and Pawan Agarwal. “Talking about code: Integrating pedagogical code reviews into early computing courses.” ACM Transactions on Computing Education (TOCE) 13.3 (2013): 14. <a href="http://dl.acm.org/citation.cfm?id=2499951">http://dl.acm.org/citation.cfm?id=2499951</a> <a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-3-return">↩</a></p></li>
<li id="2014-12-17-is-teaching-programming-like-teaching-math-footnote-4-definition" class="footnote-definition">
<p>Sondergaard, Harald. “Learning from and with peers: the different roles of student peer reviewing.” ACM SIGCSE Bulletin. Vol. 41. No. 3. ACM, 2009. <a href="http://dl.acm.org/citation.cfm?id=1562893">http://dl.acm.org/citation.cfm?id=1562893</a> <a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-4-return">↩</a></p></li>
<li id="2014-12-17-is-teaching-programming-like-teaching-math-footnote-5-definition" class="footnote-definition">
<p>Felleisen, Matthias, and Shriram Krishnamurthi. “Viewpoint Why computer science doesn’t matter.” Communications of the ACM 52.7 (2009): 37–40. <a href="http://dl.acm.org/citation.cfm?id=1538803">http://dl.acm.org/citation.cfm?id=1538803</a> <a href="#2014-12-17-is-teaching-programming-like-teaching-math-footnote-5-return">↩</a></p></li></ol></div>Too Elegant For Septemberurn:http-www-brinckerhoff-org:-blog-2013-04-04-too-elegant-for-september2013-04-04T21:18:00Z2013-04-04T21:18:00ZJohn Clements
<p>Being on sabbatical has given me a bit of experience with other systems and languages. Also, my kids are now old enough to “mess around” with programming. Learning from both of these, I’d like to hazard a bit of HtDP heresy: students should learn <code>for i = 1 to 10</code> before they learn</p>
<div class="brush: racket">
<div class="source">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre><span class="normal">1</span>
<span class="normal">2</span>
<span class="normal">3</span></pre></div></td>
<td class="code">
<div>
<pre><span></span><span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span><span class="w"> </span><span class="p">(</span><span class="n">sum</span><span class="w"> </span><span class="n">lon</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/if.html#(form._((lib._racket/private/letstx-scheme..rkt)._cond))" style="color: inherit">cond</a></span><span class="w"> </span><span class="p">[(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((lib._racket/list..rkt)._empty~3f))" style="color: inherit">empty?</a></span><span class="w"> </span><span class="n">lon</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="p">[</span><span class="k"><a href="http://docs.racket-lang.org/reference/if.html#(form._((lib._racket/private/letstx-scheme..rkt)._else))" style="color: inherit">else</a></span><span class="w"> </span><span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/generic-numbers.html#(def._((quote._~23~25kernel)._+))" style="color: inherit">+</a></span><span class="w"> </span><span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((lib._racket/list..rkt)._first))" style="color: inherit">first</a></span><span class="w"> </span><span class="n">lon</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">sum</span><span class="w"> </span><span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((lib._racket/list..rkt)._rest))" style="color: inherit">rest</a></span><span class="w"> </span><span class="n">lon</span><span class="p">)))]))</span><span class="w"></span>
</pre></div></td></tr></tbody></table></div>
</div>
<p>To many of you, this may seem obvious. I’m not writing to you. Or maybe you folks can just read along and nod sagely.</p>
<p>HtDP takes this small and very lovely thing—recursive traversals over inductively defined data—and shows how it covers a huge piece of real estate. Really, if students could just understand how to write this class of programs effectively, they would have a <em>vastly</em> easier time with much of the rest of their programming careers, to say nothing of the remainder of their undergraduate tenure. Throw a few twists in there—a bit of mutation for efficiency, some memoization, some dynamic programming—and you’re pretty much done with the programming part of your first four years.</p>
<p>The sad thing is that many, many students make it through an entire four-year curriculum without ever really figuring out how to write a simple recursive traversal of an inductively defined data structure. This makes professors sad.</p>
<p>Among the Very Simple applications of this nice idea is that of “indexes.” That is, the natural numbers can be regarded as an inductively defined set, where a natural number is either 0 or the successor of a natural number. This allows you to regard any kind of indexing loop as simply a special case of … a recursive traversal of an inductively defined data structure.</p>
<p>So here’s the problem: in September, you face a bunch of bright-eyed, enthusiastic, deeply forgiving first-year college students. And you give them the recursive traversal of the inductively defined data structure. A very small number of them get it, and they’re off to the races. The rest of them struggle, and struggle, and finally get their teammates to help them write the code, and really wish they’d taken some other class.</p>
<h3 id="nb-the-rest-of-this-makes-less-sense-even-to-me-not-finished">NB: the rest of this makes less sense… even to me. Not finished.</h3>
<p>However, another big part of the problem is … well, <a href="http://byorgey.wordpress.com/2009/01/12/abstraction-intuition-and-the-monad-tutorial-fallacy/">monads are like burritos</a>.</p>
<p>Let me take a step back.</p>
<p>The notion of repeated action is a visceral and easily-understood one. Here’s what I mean. “A human can multiply a pair of 32-bit integers in about a minute. A computer can multiply 32-bit integers at a rate of several billion per second, or about a hundred billion times as fast as a person.” That’s an easily-understood claim: we understand what it means to the same thing a whole bunch of times really fast.</p>
<p>So, when I write</p>
<p><code>for i=[1..100] multiply_two_numbers();</code></p>
<p>It’s pretty easy to understand that I’m doing something one hundred times.</p>