[racket] Top-down design, wish-lists, and testing
Thanks, we're on it. The syntax will be
(define-wish identifier [expression])
The presence of an expression will turn it into option B.
-- Matthias
On Nov 21, 2010, at 10:53 PM, Don Blaheta wrote:
> I'm teaching from HtDP2 right now and I've got a bit of a... complaint?
> Maybe more of a request. The top-down design process starts to create a
> significant cognitive load when combined with the test-driven approach,
> especially in beginner students. But I might have a solution.
>
> Here's the problem. Assuming I'm solving a non-toy problem, assuming I
> haven't yet fully broken down the problem, I'm doing top-down design as
> I go. And when I do get to the "write code" step of writing a function,
> I may see that there's something complex to be done---and I "wish" for a
> helper function.
>
> Now what? What does wishing entail?
>
> Well, I could do nothing but write it down on a piece of paper or in a
> comment. This is basically the technique described in HtDP.
> Unfortunately, this doesn't mesh well with iterative design; I want to
> be able to click Run frequently to verify that I don't have any
> "red-letter errors" (compiler errors and run-time errors), just test
> case failures. But functions that are just written on a wish list are
> not known to Racket and can't be called.
>
> As alternatives, I have two main options.
>
> 1) I can write a function stub, with minimal description and no test
> cases, just to make the error messages go away, and make sure I'm done
> with the original function (at least for now) before continuing on to
> write the helper functions.
>
> 2) I can write a description, signature, stub, and test cases for the
> helper function, and only then set it aside to add another item to the
> wish list or finish writing the original function.
>
> #2 is better in many ways, more in keeping with the test-driven
> philosophy and much less likely to be forgotten later (since the test
> cases will keep failing until I've fixed the function). However, if the
> first place I can "pause" writing the helper function is after I've
> worked out the test cases, I have *completely* lost my train of thought
> on the original function. Also, if I do anything wrong in terms of type
> mismatches, argument mismatches, or paren problems, by the time I get to
> click Run I have a lot more places to look for problems. In practice,
> #1 requires holding a lot less context in my head and eliminates the
> need to "pop the stack" (it is, if you will, a tail call); it's just
> that it's, well, unsafe. In a non-test-driven system I'd use something
> like #1, add a comment /* XXX */, and move on, but this is fairly
> antithetical to the HtDP style.
>
> Here's an example from a program I'm developing right now---I'm
> implementing the Space Invaders assignment I just gave to my students.
> I write this much:
>
> ;; defender-key : Defender KeyEvent -> Defender
> ; computes new defender position for given defender and given
> ; key; responds to left and right; stops at edges; moves by 10s
> (check-expect (defender-key 100 " ") 100)
> (check-expect (defender-key 100 "left") 90)
> (check-expect (defender-key 100 "right") 110)
> (check-expect (defender-key 0 "left") 0)
> (check-expect (defender-key WIDTH "right") WIDTH)
> (define (defender-key current key)
> (cond [(string=? key "left") ...
>
> and as I go to fill in the ... I see that it's slightly complex so I
> want to fill in with (defender-left current). I make a note of the need
> for a defender-left : Defender -> Defender in a comment, maybe with a
> little description. But if that's all I do, then when I've finished
> this function (where I also add a defender-right to the wish-list before
> I'm finished), if I click Run I still have errors. And even if I write
> out a bit of defender-left, including test cases, I can't test that
> until I've also written out a bit of defender-right. Before I'm done
> I've written more than twenty lines of stuff before I could click Run.
> And while I personally can handle that without trouble, the odds of any
> but my best students getting through that without any syntax errors is
> pretty low, and then they are in debugging hell.
>
> The solution is to make the experience more like #1 above, but without
> the unsafety. Here are three ways to do so, in decreasing level of
> implementation difficulty.
>
> PROPOSAL A: wish-for
> Add a construct to the tester library that reserves a to-be-defined
> function name and declares it as "wished for":
>
> (wish-for defender-left)
> (wish-for defender-right)
>
> This has two effects: first, the Test Results window/pane will report
> that these functions are wished for, and the implementation is therefore
> incomplete. Second, execution of any function that *calls* a wished-for
> function immediately halts with a check failure, giving a message like
>
> defender-key depends on unimplemented function defender-left in space-invaders.rkt, line 19, column 0
>
> But this is a check failure, not a runtime error, and so subsequent
> tests can be run (i.e. the student can continue working on other parts
> of their program. This proposal is minimally intrusive into the thought
> process and lets the programmer finish the original function while just
> leaving a note to themselves about the helper; it corresponds most
> closely to the design process suggested in HtDP while preserving both
> "click-Run-ability" and check safety (where "check safety" is a property
> of a function that might be defined as "either the programmer would be
> satisfied that it is complete or else at least one test case fails").
>
> PROPOSAL B: wish-stub
> Add a construct to the tester library that stubs a to-be-defined
> function name with a dummy value, and declares it as "wished for":
>
> (wish-stub defender-left 0)
> (wish-stub defender-right 0)
>
> Similar to A but includes a dummy value of the appropriate type.
> Implicitly declares a vararg function that always returns the stubbed
> dummy value a la
>
> (define defender-left (lambda arglst 0)) ; autodefined by wish-stub
>
> but also causes the Test Results window/pane to report these functions
> as wished-for. Compared to A, this version is only a slight added
> cognitive load for the programmer (having to understand the role of the
> helper well enough to give a valid dummy output value of the correct
> type), but it removes the need for program tracing and/or exception
> handling---since the helper will now return a valid value, the original
> function will simply have a normal check failure. However, the
> programmer doesn't have to worry about half-assing some test cases or
> omitting test cases and forgetting to fill in the stub later.
>
> PROPOSAL C: check-stub
> Add a construct to the tester library that declares a particular
> function as "wished for", essentially just an always-failing test case.
>
> (define (defender-left current)
> 0)
> (check-stub defender-left)
>
> This is a simplification of B that puts the onus back on the user to
> actually declare the function. The reason I prefer B to this is that
> it's more of an intrusion on the design process, and in particular, you
> need to either come up with good argument names right away or else toss
> in something you haven't thought about enough---and the whole goal of
> this proposal is to *minimise* the amount of stack that the (student)
> programmer needs to maintain. Also, if the user forgets to remove a
> wish-stub declaration when they define the function for real, this is
> easily identified as a conflict (because both are trying to define the
> function), while it's harder for a lingering check-stub to notice that
> the corresponding function has a "real" function body now.
>
>
>
> Sorry, that got long; I've been thinking about this for a few months
> now. Let me know what y'all think; I may try to put together a
> teachpack over winter break.
>
> --
> -=-Don Blaheta-=-dblaheta at monm.edu-=-=-<http://www.monmsci.net/~dblaheta/>-=-
> This sentence contradicts itself---no actually it doesn't.
> --Douglas Hofstadter
> _________________________________________________
> For list-related administrative tasks:
> http://lists.racket-lang.org/listinfo/users