tag:blogger.com,1999:blog-183023932021-09-15T14:33:41.284-07:00hashcollisionConfusion is the natural (dis-)order of things. Defy it.Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.comBlogger9125tag:blogger.com,1999:blog-18302393.post-61933493562013023772007-05-09T11:43:00.000-07:002007-05-09T12:51:39.238-07:00aliasing renamingPeople are often particular about naming. Names still hold special power: we may rationalize that names don't matter, but there's something hardcoded in human beings about choosing the right name for a thing. It's a hard thing to fight.<br /><br />Programmers are people too, and programmers also keep concerns (sometimes too much) about the right name for a thing. As a concrete example, the word <i>lambda</i> scares off a few people since it's a foreign word. We might like to be <a href="http://www.paulgraham.com/arclessons.html">brief</a> and be able to say:<br /><blockquote><pre><br /> (def (make-adder x)<br /> (fn (y) (+ x y)))<br /></pre></blockquote><br />where <tt>def</tt> and <tt>fn</tt> are really meant to behave like <tt>define</tt> and <tt>lambda</tt>, respectively. One nice thing about Scheme is that it's plastic enough to absorb a trivial change like this. Here's a small library for aliasing identifiers:<br /><blockquote><pre><br />(module alias mzscheme<br /> ;; Practice writing macros that themselves generate macros. <br /> <br /> (provide alias)<br /> <br /> ;; (alias old-keyword new-keyword) SYNTAX<br /> ;; Creates a new syntax for new-keyword; any occurrence of new-keyword<br /> ;; is replaced with the old-keyword.<br /> (define-syntax (alias stx-1)<br /> (syntax-case stx-1 ()<br /> [(_1 orig new)<br /> (syntax/loc stx-1<br /> (define-syntax (new stx-2)<br /> (syntax-case stx-2 ()<br /> [_2<br /> (identifier? stx-2)<br /> (syntax/loc stx-2<br /> orig)]<br /> [(_2 e (... ...))<br /> (syntax/loc stx-2<br /> (orig e (... ...)))])))])))<br /></pre></blockquote><br /><br />Once we have a tool like this, then we can test it out:<br /><blockquote><pre><br />(module test-alias mzscheme<br /> (require "alias.ss")<br /> (alias lambda fn)<br /> (alias define def)<br /> <br /> (def (make-adder x)<br /> (fn (y) (+ x y))))<br /></pre></blockquote><br /><br />That being said, the above example is a silly thing to do. Even in competent hands, this is easy to abuse; in the wrong hands, one can end up with something that belongs to an obfuscation contest.<br /><blockquote><pre><br />> (alias alias a)<br />> (a define d)<br />> (a if i)<br />> (d (f x) (i (= x 0) 1 (* x (f (- x 1)))))<br />> (f 3)<br />6<br /></pre></blockquote><br /><br />Communication between people means that we "come to terms", and the inherent abuse here is to turn the language into something that no one else can read. In PLT Scheme's case, the above is especially bad because there's already an established mechanism for controlled renaming of identifiers as part of PLT Scheme's module system.<br /><blockquote><pre><br />(module test-alias mzscheme<br /> (require (rename mzscheme fn lambda)<br /> (rename mzscheme def define))<br /> <br /> (def (make-adder x) (fn (y) (+ x y))))<br /></pre></blockquote><br /><br />Still, I thought the macro definition was cute (even if it's silly), and it's one of the more direct applications of a "macro-generating macro" that I've seen. But maybe this is all just syntax, futzing with words, distracting us from the real work on nailing down the semantics of our thoughts.Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.com0tag:blogger.com,1999:blog-18302393.post-76993603395862165062007-05-02T13:05:00.000-07:002007-05-04T11:01:48.842-07:00logicalI didn't get this so strongly before, but there's a correspondence between the language and structures of first-order logic, and the signatures and units of PLT Scheme (as well as the signatures and structures of ML). I guess the analogy is something like: "language::signature as structure::unit."<br /><br />As a concrete example, a first-order language might just have equality (=), and a 2-arity P relation. Once we have a language, we can start writing statements in that language. Here are two statements using the language:<br /><ul><br /><li> for all x, y, z: P(x, y) and P(x, z) implies y = z </li><br /><li> for all x: there exists y: P(x, y) </li><br /></ul><br />These two sentences, taken together, are trying to say "P must be a relation that represents a total function". These sentences don't have a true or false value without being evaluated in the context of some structure. A structure tells us what domain of thingies (the universe) we're iterating over when we say "for all" or "there exists", and also provides a concrete definition for the relations of the language.<br /><br />There are some structures that might satisfy this, and other structures that don't. Structures that satisfy the sentences we care about are called "models". One example of such a model for those two sentences might be the natural numbers (0, 1, 2, ...), where the interpretation of P is:<br /><blockquote><br /> P(x, y) if and only if y = square(x)<br /></blockquote><br />But an example of a similar structure that wouldn't fit the above conditions would be natural numbers under a P that's not a function relation, like:<br /><blockquote><br /> P(x, y) if and only if x < y<br /></blockquote><br />and so we'd say that this structure isn't a model of those sentences.<br /><br />Programmers have an intuitive idea about this: it's similar to the distinction between "interface" and "implementation": the language provides the interface, and the structures provide implementations for that language. And when we have an interface, we usually have some implicit idea of how that interface should behave; if we have an interface called <i>Stack</i>, we have an expectation about what it should do. We can make those assumptions explicit by writing contracts or predicates with that interface: the things that satisfy our expectations are good implementations.<br /><br />Again, to make this concrete, when we're programming with signatures and units, we first have to specify the things we're going to be fiddling with. For example, a signature like:<br /><blockquote><pre><br /> ;; A function^ model consists of a universe of elements,<br /> ;; and a 2-place predicate P between elements.<br /> (define-signature function^ (universe<br /> P))<br /></pre></blockquote><br />gives us a language for talking and using units that implement this signature. For example, to build a unit where P is the squaring relation, we can do this:<br /><blockquote><pre><br /> (define-unit squaring@<br /> (import)<br /> (export function^)<br /> (define universe (list 0 1 2 3 4 '>4))<br /> (define (P x y)<br /> (cond <br /> [(equal? x '>4)<br /> (equal? y '>4)]<br /> [(> (* x x) 4)<br /> (equal? y '>4)]<br /> [else<br /> (equal? y (* x x))])))<br /></pre></blockquote><br />(I'm defining the P function a bit peculiarly since I want the universe of elements to be finite.)<br /><br />But again, not all implementations of the <tt>function^</tt> signature will do the right thing. Here's a structure that doesn't satisfy the intent of the function^ signature.<br /><blockquote><pre><br /> (define-unit less-than@<br /> (import)<br /> (export function^)<br /> (define universe '(1 2 3))<br /> (define (P x y)<br /> (< x y)))<br /></pre></blockquote><br /><br />How do we capture intent? We want to be able to catch implementations that don't do what we expect, and one way is to write code to exercise the units. Here's one example:<br /><blockquote><pre><br /> ;; function-model?: function@ -> boolean<br /> ;; Checks to see if the given unit satisfies what we<br /> ;; expect out of functions, that their domain is total<br /> ;; and that each element of the domain maps to<br /> ;; just one element of the range.<br /> ;;<br /> ;; In first-order logic:<br /> ;; (for all x, y, z: P(x, y) and P(x, z) implies y = z)<br /> ;; and<br /> ;; (for all x: there exists y: P(x, y))<br /> (define (function-model? model@)<br /> (local ((define-unit-binding a-model@<br /> model@ (import) (export function^))<br /> <br /> (define-unit function-tester@<br /> (import function^)<br /> (export)<br /> (and (for-all* (lambda (x y z)<br /> (implies? (and (P x y) (P x z))<br /> (equal? y z)))<br /> universe universe universe)<br /> (for-all* (lambda (x)<br /> (exists? (lambda (y) (P x y))<br /> universe))<br /> universe))))<br /> (invoke-unit<br /> (compound-unit/infer<br /> (import)<br /> (export)<br /> (link a-model@ function-tester@)))))<br /></pre></blockquote><br />(There are a few definitions I've left out here; see <a href="http://hashcollision.org/svn/repos/projects/plt-misc/logic-practice.ss">logic-practice.ss</a> for the other definitions.)<br /><br />Once we have this, we can now check to see if <tt>squaring@</tt> and <tt>less-than@</tt> satisfy the <tt>function-model?</tt> predicate.<br /><pre><br />> (function-model? squaring@)<br />#t<br />> (function-model? less-than@)<br />#f<br /></pre>Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.com2tag:blogger.com,1999:blog-18302393.post-85707078241957638162007-04-20T08:13:00.000-07:002007-04-20T08:56:09.232-07:00function of the day: foldThe fold function does very much what the name implies: it's meant to fold ingredients into a big mixing bowl, of course. Here's a definition:<br /><br /><blockquote><pre><br />;; fold: (ingredient bowl -> bowl) bowl (listof ingredient) -> bowl<br />;; Folds in a list of ingredients into a bowl with a folding-action.<br />(define (fold folding-action a-bowl ingredients)<br /> (cond<br /> [(empty? ingredients) a-bowl]<br /> [else<br /> (local ((define mixed-bowl (folding-action<br /> (first ingredients)<br /> a-bowl)))<br /> (fold folding-action mixed-bowl (rest ingredients)))]))<br /></pre></blockquote><br /><br />FOLD here adds an ingredient at a time, using the folding-action to mix in every ingredient thoroughly into our large bowl. Once we have this incorporating function, we can roll up our sleeves and whisk some pancake batter:<br /><br /><blockquote><pre><br />;; make-pancake-batter: -> string<br />;; Example of FOLD to make pancake batter.<br />(define (make-pancake-batter)<br /> (local ((define (whisk an-ingredient a-bowl)<br /> (string-append a-bowl an-ingredient))<br /> <br /> (define pancake-ingredients '("flour" "sugar" "eggs" "milk"))<br /> <br /> (define empty-bowl ""))<br /> (fold whisk empty-bowl pancake-ingredients)))<br /></pre></blockquote><br />Oh! I forgot the baking powder, so those pancakes will be a little stiff. Oh well.<br /><br />Of course, we might even want to whisk in a slightly different way:<br /><blockquote><pre><br /> (local<br /> ((define (whisk an-ingredient a-bowl)<br /> (cond [(string=? an-ingredient "eggs")<br /> (string-append a-bowl "egg-white-only")]<br /> [else<br /> (string-append a-bowl an-ingredient)])))<br /> (fold whisk "" '("flour" "eggs" "cabbage")))<br /></pre></blockquote><br />Now we've got the basics of an okonomiyaki, I suppose.<br /><br />We might even be silly enough to mix things that aren't even food!<br /><blockquote><pre><br />> (fold + 0 '(1 2 3 4 5))<br />15<br /></pre></blockquote><br />but this seems like a boring example compared to whisking.<br /><br />Folding is popular, but for some reason it goes by different names by different programming camps. Python programmers call it <a href="http://www.python.org/doc/lib/built-in-funcs.html#l2h-60">REDUCE</a>, and Ruby programmers call it <a href="http://www.ruby-doc.org/core/classes/Enumerable.html#M003171">INJECT</a>. I don't know about these names, though: I think they sound a little bit aggressive for my taste. As for me, I like the homeliness of "fold": it reminds me of the kitchen.Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.com4tag:blogger.com,1999:blog-18302393.post-34575342159073803612007-03-30T14:47:00.000-07:002007-03-30T15:06:33.046-07:00birthdayI felt a little silly today, and just learned about SRFI 42 (Eager Comprehensions), so I thought I might play with it on a trivial example.<br /><blockquote><br /><pre><br />(module happy-birthday-song mzscheme<br /> (require (lib "etc.ss")<br /> (lib "42.ss" "srfi"))<br /> <br /> (provide sing)<br /><br /> ;; sing: string -> void<br /> ;; Prints out the popular happy birthday stanza.<br /> (define (sing person)<br /> (local ((define (trailer i)<br /> (cond [(= i 2)<br /> (format "dear ~a" person)]<br /> [else<br /> "to you"])))<br /> (do-ec (: i 4)<br /> (begin<br /> (display "Happy birthday ")<br /> (display (trailer i))<br /> (newline))))))<br /></pre><br /></blockquote>Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.com0tag:blogger.com,1999:blog-18302393.post-58703875805562819392007-02-08T22:49:00.000-08:002007-02-01T19:41:58.303-08:00crazedI think I came out a bit raving during Thursday's <a href="http://web.cs.wpi.edu/~gpollice/Dojo.html">Coding Dojo</a>; I did a quick-and-dirty derivation of the brute-force countdown program for last afternoon's session, and I only had an hour to work with. Not only that, but I had to try to explain what I was doing while coding things up, and that almost inevitably gets me into trouble. And I needed to write unit tests too. And make unintentional mistakes to justify those unit tests. I'll show one of those mistakes at the end of this post.<br /><br />Actually, it was really exciting! But my body felt so totally exhausted at the end of it. Much of it is performance, exaggerations and all. My stage persona is one that's, well, a bit crazed. I'm not exactly sure what the audience thought about it. I hope folks thought was worth it and that the content was useful.<br /><br />The function where I made a mistake was this: we were writing a function to see if any number expression was reused. One way to check for this condition is to do a depth-first traversal across the expression, watching to see if any number appears twice.<br /><br />Here's a variation of what I did:<br /><blockquote><pre><br />(require (lib "plt-match.ss"))<br /><br />;; Data structures, of course, for our expressions:<br />(define-struct num (n))<br />(define-struct comb (left op right))<br /><br />;; is-legal?: expr -> boolean<br />;; Returns true if no num is reused more than once in the expression<br />;; or its subexpressions.<br />(define (is-legal? expr)<br /> (define ht (make-hash-table))<br /> <br /> (define (seen-already? expr)<br /> (hash-table-get ht expr #f))<br /> <br /> (define (mark-as-seen! expr)<br /> (hash-table-put! ht expr #t))<br /> <br /> (let loop ([expr expr])<br /> (match expr<br /> [(struct num (n))<br /> (cond<br /> [(seen-already? n) #f]<br /> [else<br /> (mark-as-seen! expr)<br /> #t])]<br /> [(struct comb (l o r))<br /> (and (loop l) (loop r))])))<br /></pre></blockquote><br /><br />Thankfully, I also wrote a test case.<br /><blockquote><pre><br />(require (planet "test.ss" ("schematics" "schemeunit.plt"))<br /> (planet "text-ui.ss" ("schematics" "schemeunit.plt")))<br /><br />(define simple-test-suite<br /> (test-suite<br /> "testing"<br /> (test-true "check simple case"<br /> (is-legal? (make-num 3)))<br /> (let ([n1 (make-num 4)])<br /> (test-false "check false"<br /> (is-legal? (make-comb n1 + n1))))))<br /><br />(test/text-ui simple-test-suite)<br /></pre></blockquote><br /><br />And the test case quickly showed that I screwed up somewhere.<br /><blockquote><pre><br />Welcome to DrScheme, version 369.6-svn24jan2007 [3m].<br />Language: Pretty Big (includes MrEd and Advanced Student).<br />testing > check false<br />check false has a FAILURE<br />name: check-false<br />location: struct:object:...ascheme/diva-link.ss:124:4>:39:5<br />params: (#t)<br />1 success(es) 1 failure(s) 0 error(s) 2 test(s) run<br /></pre></blockquote><br /><br />Oh no! Terror! But thankfully, I figured out I had made a type error: the hash-table should always hold expressions, and to my chagrin, I stuffed the primitive number into it instead. Easy to fix.<br /><blockquote><pre><br />(define (is-legal? expr)<br /> (define ht (make-hash-table))<br /> <br /> (define (seen-already? expr)<br /> (hash-table-get ht expr #f))<br /> <br /> (define (mark-as-seen! expr)<br /> (hash-table-put! ht expr #t))<br /> <br /> (let loop ([expr expr])<br /> (match expr<br /> [(struct num (n))<br /> (cond<br /> [(seen-already? expr) #f]<br /> [else<br /> (mark-as-seen! expr)<br /> #t])]<br /> [(struct comb (l o r))<br /> (and (loop l) (loop r))])))<br /></pre></blockquote><br />And hesitantly pressed <b>Run</b> on my tests.<br /><blockquote><pre><br />Welcome to DrScheme, version 369.6-svn24jan2007 [3m].<br />Language: Pretty Big (includes MrEd and Advanced Student).<br />2 success(es) 0 failure(s) 0 error(s) 2 test(s) run<br /></pre></blockquote><br />Hurrah.<br /><br />In all, it was a fun day.Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.com3tag:blogger.com,1999:blog-18302393.post-15103847237973755262007-02-01T18:03:00.000-08:002007-02-01T19:41:58.363-08:00countdownRecently, I've been going to a <a href="http://web.cs.wpi.edu/%7Egpollice/Dojo.html">Coding Dojo</a> to practice my programming chops. One of the problems presented so far has been the <a href="http://rubyquiz.com/quiz7.html">Countdown</a> program from <a href="http://rubyquiz.com/">RubyQuiz</a>. I thought I might give it a shot, and <a href="http://www2.blogger.com/post-edit.g?blogID=18302393&postID=1510384723797375526">cooked</a> up a solution in Scheme, and it wasn't too bad.<br /><br />I'll try to give an write-up on how one might attack the problem starting from scratch, and rather than just give a perfect solution, I'll go at this iteratively. (And make accidental mistakes along the way!)<br /><br />The idea behind the countdown problem is that we are doing exhaustive "brute-force" search for all arithmetic expressions. For example, if we limit ourselves to the numbers 3, 4, and 5, and the only operations are addition and multiplication, here's a conceptual trace of what expressions we'll be brutishly looking at:<br /><blockquote><br />3<br />4<br />5<br /></blockquote><br />That the first phase. Then we take pairs of numbers here, and build new expressions:<br /><blockquote><br />3 + 4<br />3 + 5<br />4 + 5<br />3 * 4<br />3 * 5<br />4 * 5<br /></blockquote><br />Ok, now we take pairs again, combining stuff from previous phases and the phase we just generated:<br /><blockquote><br />3 + (4 + 5)<br />3 + (4 * 5)<br />4 + (3 + 5)<br />4 + (3 * 5)<br />5 + (3 + 4)<br />5 + (3 * 5)<br />3 * (4 + 5)<br />3 * (4 * 5)<br />4 * (3 + 5)<br />4 * (3 * 5)<br />5 * (3 + 4)<br />5 * (3 * 5)<br /></blockquote><br />Whew! Even with three numbers, this gets a bit long.<br /><br />I should be more formal for a moment. By an expression, I mean either a number, or something that involves two smaller expressions and a number. In Backus-Naur Form, that's:<br /><blockquote><pre><br /> expression = number<br /> | expression op expression<br /></pre></blockquote><br />Let's create a data definition in Scheme for this.<br /><blockquote><pre><br />(module countdown mzscheme<br /> (provide (all-defined))<br /> ;; An expression is either a<br /> ;; (make-num n)<br /> ;; where n is a number, or<br /> ;; (make-comb l o r)<br /> ;; where l and r are expressions, and op is an operator.<br /> (define-struct num (n))<br /> (define-struct comb (left op right)))<br /></pre></blockquote><br />Ok, we have our expression data definition. We should also make a quick-and-dirty helper function to help us visually read expressions; let's call this expression->string:<br /><blockquote><pre><br />(module countdown mzscheme<br /> (require (lib "plt-match.ss"))<br /><br /> ;; [data definition omitted]<br /><br /> ;; expression->string: expression -> string<br /> ;; Makes a nice human-readable expression.<br /> (define (expression->string an-expr)<br /> (define (operator->string an-op)<br /> (cond<br /> [(eq? an-op +) "+"]<br /> [(eq? an-op *) "*"]))<br /> <br /> (match an-expr<br /> [(struct num (n)) (format "~a" n)]<br /> [(struct comb (l o r))<br /> (format "(~a ~a ~a)"<br /> (expression->string l)<br /> (operator->string o)<br /> (expression->string r))]))</pre></blockquote><br /><br />Ok, now that we've got this, let's exercise the code.<br /><blockquote><pre><br />> (expression->string (make-comb (make-num 3) + (make-num 4)))<br />"(3 + 4)"<br /></pre></blockquote><br />Good. But I'm feeling guilty; we should really make this a unit test. Let's pull in <a href="http://planet.plt-scheme.org/#schemeunit.plt">SchemeUnit</a>, the unit testing framework for Scheme.<br /><pre><blockquote><br />(module countdown mzscheme<br /> (require ;; [other packages omitted]<br /> (planet "test.ss" ("schematics" "schemeunit.plt" 2))<br /> (planet "text-ui.ss" ("schematics" "schemeunit.plt" 2)))<br /> <br /> ;; [code omitted]<br /><br /> (define countdown-tests<br /> (test-suite<br /> "tests for countdown"<br /> (test-equal?<br /> "expression->string simple test"<br /> (expression->string (make-comb (make-num 3) + (make-num 4)))<br /> "(3 + 4)")))<br /><br /> (test/text-ui countdown-tests))<br /></pre></blockquote><br />If we run this module, we see:<br /><pre><blockquote><br />1 success(es) 0 failure(s) 0 error(s) 1 test(s) run<br />> <br /></pre></blockquote><br />Very good.<br /><br />Now let's see if we can build new expressions from old ones. The idea is that, if we have an existing collection of expressions, we want to pair two of them up together and do something with them. We might do this naively:<br /><blockquote><pre><br /> ;; for-each-pairs: (X X -> void) (listof x) -> void<br /> ;; Calls f on distinct ordered pairs in the collection.<br /> (define (for-each-pairs f collection)<br /> (let outer-loop ([i 0])<br /> (when (< i (length collection))<br /> (let inner-loop ([j (add1 i)])<br /> (when (< j (length collection))<br /> (f (list-ref collection i) (list-ref collection j))<br /> (inner-loop (add1 j))))<br /> (outer-loop (add1 i)))))<br /></pre></blockquote><br />Does this work?<br /><blockquote><pre><br />> (for-each-pairs (lambda (x y) (printf "~a ~a~n" x y)) '(red blue green))<br />red blue<br />red green<br />blue green<br /></pre></blockquote><br />Looks reasonable. Of course, now we want to use this to collect up all the pairs and build an addition and a multiplication from <tt>x</tt> and <tt>y</tt>. Let's call this <tt>build-next-generation</tt>. Oh, let's write our unit test first:<br /><blockquote><pre><br /> (test-equal?<br /> "build-next-generation simple"<br /> (map expression->string<br /> (build-next-generation (list (make-num 3)<br /> (make-num 4))))<br /> (list "(3 + 4)" "(3 * 4)"))<br /></pre></blockquote><br />Ok, let's roll up our sleeves and do it.<br /><blockquote><pre><br /> ;; build-next-generation: (listof expression) -> (listof expression)<br /> ;; Constructs new expressions from pairs of old ones.<br /> (define (build-next-generation expressions)<br /> (define next-gen '())<br /> <br /> (define (collect! an-expr)<br /> (set! next-gen (cons an-expr next-gen)))<br /> <br /> (define (visit-pair e1 e2)<br /> (collect! (make-comb e1 + e2))<br /> (collect! (make-comb e1 * e2)))<br /> <br /> (for-each-pairs visit-pair expressions)<br /> (reverse! next-gen)<br /> next-gen)<br /></pre></blockquote><br />Ok, should work. On every pair, we combine two expressions, and collect them. We do a <tt>reverse!</tt> to put things in the order expected by the test case. (Although that might be a little silly.) Let's rerun our unit tests.<br /><blockquote><pre><br />tests for countdown > build-next-generation simple<br />build-next-generation simple has a FAILURE<br />name: check-equal?<br />location: #<struct:object:...ascheme/diva-link.ss:124:4>:67:5<br />params: (("(3 * 4)") ("(3 + 4)" "(3 * 4)"))<br />actual: ("(3 * 4)")<br />expected: ("(3 + 4)" "(3 * 4)")<br />1 success(es) 1 failure(s) 0 error(s) 2 test(s) run<br /></pre></blockquote><br />What?! Yes, there's something wrong here.<br /><br />It turns out that I've misused <tt>reverse!</tt> here: I should just take the result of <tt>reverse!</tt> and return it directly.<br /><blockquote><pre><br /> (define (build-next-generation expressions)<br /> (define next-gen '())<br /> <br /> (define (collect! an-expr)<br /> (set! next-gen (cons an-expr next-gen)))<br /> <br /> (define (visit-pair e1 e2)<br /> (collect! (make-comb e1 + e2))<br /> (collect! (make-comb e1 * e2)))<br /> <br /> (for-each-pairs visit-pair expressions)<br /> (reverse! next-gen))<br /></pre></blockquote><br />Hurrah for unit tests. But maybe we shouldn't do the reverse after all. Let me just fix up the test case to expect the paired expressions in a different order. Since I've made so many changes, let's show what the code looks like now:<br /><blockquote><pre><br />(module countdown mzscheme<br /> (provide (all-defined))<br /> (require (lib "plt-match.ss")<br /> (planet "test.ss" ("schematics" "schemeunit.plt" 2))<br /> (planet "text-ui.ss" ("schematics" "schemeunit.plt" 2)))<br /> <br /> ;; An expression is either a<br /> ;; (make-num n)<br /> ;; where n is a number, or<br /> ;; (make-comb l o r)<br /> ;; where l and r are expressions, and op is an operator.<br /> (define-struct num (n))<br /> (define-struct comb (left op right))<br /> <br /> <br /> ;; expression->string: expression -> string<br /> ;; Makes a nice human-readable expression.<br /> (define (expression->string an-expr)<br /> (define (operator->string an-op)<br /> (cond<br /> [(eq? an-op +) "+"]<br /> [(eq? an-op *) "*"]))<br /> <br /> (match an-expr<br /> [(struct num (n)) (format "~a" n)]<br /> [(struct comb (l o r))<br /> (format "(~a ~a ~a)"<br /> (expression->string l)<br /> (operator->string o)<br /> (expression->string r))]))<br /> <br /> ;; for-each-pairs: (X X -> void) (listof x) -> void<br /> ;; Calls f on distinct ordered pairs in the collection.<br /> (define (for-each-pairs f collection)<br /> (let outer-loop ([i 0])<br /> (when (< i (length collection))<br /> (let inner-loop ([j (add1 i)])<br /> (when (< j (length collection))<br /> (f (list-ref collection i) (list-ref collection j))<br /> (inner-loop (add1 j))))<br /> (outer-loop (add1 i)))))<br /> <br /> <br /> ;; build-next-generation: (listof expression) -> (listof expression)<br /> ;; Constructs new expressions from pairs of old ones.<br /> (define (build-next-generation expressions)<br /> (define next-gen '())<br /> <br /> (define (collect! an-expr)<br /> (set! next-gen (cons an-expr next-gen)))<br /> <br /> (define (visit-pair e1 e2)<br /> (collect! (make-comb e1 + e2))<br /> (collect! (make-comb e1 * e2)))<br /> <br /> (for-each-pairs visit-pair expressions)<br /> next-gen)<br /> <br /> <br /> (define countdown-tests<br /> (test-suite<br /> "tests for countdown"<br /> (test-equal?<br /> "expression->string simple test"<br /> (expression->string (make-comb (make-num 3) + (make-num 4)))<br /> "(3 + 4)")<br /> (test-equal?<br /> "build-next-generation simple"<br /> (map expression->string<br /> (build-next-generation (list (make-num 3)<br /> (make-num 4))))<br /> (list "(3 * 4)" "(3 + 4)"))))<br /> <br /> (test/text-ui countdown-tests))<br /></pre></blockquote><br />Ok! Does this do what we'll need for brute-forcing the countdown problem?<br /><blockquote><pre><br />> (define expressions (list (make-num 3)<br /> (make-num 4)<br /> (make-num 5)))<br />> (define generation-1 (append expressions (build-next-generation expressions)))<br />> (define generation-2 (append generation-1 (build-next-generation generation-1)))<br />> (map expression->string generation-2)<br />("3"<br /> "4"<br /> "5"<br /> "(4 * 5)"<br /> "(4 + 5)"<br /> "(3 * 5)"<br /> "(3 + 5)"<br /> "(3 * 4)"<br /> "(3 + 4)"<br /> "((3 * 4) * (3 + 4))"<br /> "((3 * 4) + (3 + 4))"<br /> "((3 + 5) * (3 + 4))"<br /> ;; ... a LOT of output omitted<br /> "(3 * (4 * 5))"<br /> "(3 + (4 * 5))"<br /> "(3 * 5)"<br /> "(3 + 5)"<br /> "(3 * 4)"<br /> "(3 + 4)")<br /></pre></blockquote><br />It's a beginning. It's not quite right, but it's getting there. Since this post is getting long, I'll split this up with another blog post later.Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.com0tag:blogger.com,1999:blog-18302393.post-1167357265681675942006-12-28T17:54:00.000-08:002007-01-30T12:09:16.717-08:00debugging test-caseI thought it might be useful to do a stream-of-consciousness post about what a programmer does when he or she sees a bug and wants to fix it.<br /><br />The bug in question is in DrScheme, and it's triggered whenever one goes to the Special menu and runs an "Insert Test Case". From that point on, the resulting program is uncompilable, with the following error message:<br /><br /><blockquote><br /><code><br />require: broken compiled code (phase 0, defn-phase 0): cannot find module |,/Users/dyoo/local/plt/collects/test-suite/private/test-case| in: test-case<br /></code><br /></blockquote><br /><br />The first thing I wanted to do was isolate the problem: what code is responsible? I knew the functionality in question had something to do with "Insert Test Case", so let's start there.<br /><blockquote><br /><pre><br />mithril:~ dyoo$ cd local/plt/collects/<br />mithril:~/local/plt/collects dyoo$ cd string-constants/<br />mithril:~/local/plt/collects/string-constants dyoo$ grep 'Insert Test Case' *<br />english-string-constants.ss: (test-case-insert "Insert Test Case")<br />portuguese-string-constants.ss: (test-case-insert "Insert Test Case")<br /></pre><br /></blockquote><br />I had some previous knowledge that the menus in DrScheme were all internationalized, so I could start by searching through the string-constants directory to start my search. Whatever code uses <i>test-case-insert</i> is probably related, so we should start searching for occurrences of that now.<br /><blockquote><br /><pre><br />mithril:~/local/plt/collects/string-constants dyoo$ cd ..<br />mithril:~/local/plt/collects dyoo$ grep -r test-case-insert *<br /></pre><br /></blockquote><br />A bunch of output spews out, but I'll ignore anything dealing with string constants; if I do that, I see that there's a particular collection called test-suite, and that sounds promising.<br /><br />Next thing to do: let's see if anyone else has already tried looking at this, by searching http://bugs.plt-scheme.org. Yup, there appear to be a few. Bugs <a href="http://bugs.plt-scheme.org/query/?cmd=view%20audit-trail&database=default&pr=7001&return_url=http%3A%2F%2Fbugs.plt-scheme.org%2Fquery%2F%3Fdatabase%3Ddefault%3Bdebug%3D%3Bcmd%3Dsubmit%2520query%3BState%3Dany%3Bignoreclosed%3DIgnore%2520Closed%3BSynopsis%3D%3Bmultitext%3Dbroken%2520compiled%2520code%3Bcolumns%3DSynopsis%3Bsortby%3DNumber%20">7001</a>, <a href="http://bugs.plt-scheme.org/query/?cmd=view%20audit-trail&database=default&pr=7317&return_url=http%3A%2F%2Fbugs.plt-scheme.org%2Fquery%2F%3Fdatabase%3Ddefault%3Bdebug%3D%3Bcmd%3Dsubmit%2520query%3BState%3Dany%3Bignoreclosed%3DIgnore%2520Closed%3BSynopsis%3D%3Bmultitext%3Dbroken%2520compiled%2520code%3Bcolumns%3DSynopsis%3Bsortby%3DNumber%20">7317</a>, and <a href="http://bugs.plt-scheme.org/query/?cmd=view%20audit-trail&database=default&pr=7373&return_url=http%3A%2F%2Fbugs.plt-scheme.org%2Fquery%2F%3Fdatabase%3Ddefault%3Bdebug%3D%3Bcmd%3Dsubmit%2520query%3BState%3Dany%3Bignoreclosed%3DIgnore%2520Closed%3BSynopsis%3D%3Bmultitext%3Dbroken%2520compiled%2520code%3Bcolumns%3DSynopsis%3Bsortby%3DNumber">7373 </a> seem to be the same bug. Ok, time to read those bugs reports thoroughly just to make sure I understand what other people have said about this.<br /><br />Ok, it looks like Robby mentioned a workaround: if the user enters:<br /><blockquote><br /><pre><br />(require (lib "test-case.ss" "test-suite" "private"))<br /></pre><br /></blockquote><br />then things should work out. Yup, I can verify that this works. But that seems really odd to have to do this!<br /><br />I'll start to look at test-suite and see what the overall flow of the program is.<br /><br />[will fill in as soon as I finish doing a review]<br /><br />[hours pass...]<br /><br />Well, I traced the issue down to how the snips make external requirements that can't always be met, especially in modularized code. I posted a <a href="http://list.cs.brown.edu/pipermail/plt-scheme/2006-December/015844.html">question</a> about it in the PLT list, and then, a few days later, they were <a href="http://list.cs.brown.edu/pipermail/plt-scheme/2007-January/015876.html">obliterated</a> altogether.<br />So, in summary: the code I studied is no longer relevant. It happens.Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.com0tag:blogger.com,1999:blog-18302393.post-1165519641148975012006-12-07T11:27:00.000-08:002007-01-30T12:10:04.083-08:00heresyEvery so often, a fey mood takes me, and I write a program that probably shouldn't see the light of day. I've done so with a PLT package called <a href="http://planet.plt-scheme.org/#infix.plt">infix</a>. It's a package for writing infix expressions in PLT Scheme, but it does some other funky stuff. For example:<br /><code></code><pre><br />(module test mzscheme<br /> (require (planet "infix.ss" ("dyoo" "infix.plt" 1 0)))<br /><br /> (infix printf("hello, this is a test: ~a ~a~n",<br /> list(3, 4),<br /> 5))<br /><br /> (define (in-order a b c)<br /> (infix printf("in-order(~a, ~a, ~a): ~a~n",<br /> a, b, c, a <= b <= c))) (infix in-order(1, 2, 3)) (infix in-order(1, 3, 2))) </pre><br />I had a very good laugh with Guillaume when I showed this to him. You have to be a Schemer to appreciate how very wrong this is.<br /><br />This technique is a proof-of-concept that shows just how programmable the PLT Scheme syntax system can be. It wouldn't be much more work to embed a different programming language syntax in here, as long as it's compatible with s-expressions. The source code for this can be found <a href="http://hashcollision.org/svn/repos/projects/infix/">here</a>.Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.com0tag:blogger.com,1999:blog-18302393.post-1161024575244115122006-10-16T11:49:00.000-07:002007-01-30T12:10:29.601-08:00in summation...In an earlier <a href="http://hashcollision.blogspot.com/2006/09/new-car-brand-toyota-type-corolla.html">post</a>, I wondered out how to get a closed form for my car debt. The program I wrote can be actually thought of as a <span style="font-style: italic;">recurrence relation</span>:<br /><pre><br /> debt at the very beginning = initial debt at zero months<br /><br /> debt(n) = monthly interest * (debt from last month - my monthly car payment)<br /> and where n has to be greater than 0.<br /></pre><br />But since that's a bit wordy, I'll use some shorter variable names when I'm talking about the debt I'm incurring at month <span style="font-weight: bold;">n</span>:<br /><pre><br /> d(n) = I * (d(n-1) - P) for n > 0<br /> = I * d(n-1) - I*P for n > 0<br /></pre><br />I didn't quite remember how to get a closed form for this formula at the time. Yesterday, though, as I was quaffing a coffee at Cape Cod, I flipped through <a href="http://www-cs-faculty.stanford.edu/%7Eknuth/gkp.html">Concrete Mathematics</a>. There in the first few pages of the book was the technique I needed!<br /><br />The core idea is to use a summation factor to make the function I'm solving for a little simpler. For the recurrence above,<br /><pre><br /> (1/I)^n<br /></pre><br />is a good summation factor. When I multiply my recurrence with it, I get:<br /><pre><br /> d(n)/(I^n) = d(n-1)/(I^(n-1)) - P/(I^(n-1)) for n > 0<br /></pre><br />The reason this is simpler is because I can consider a parallel formula:<br /><pre><br /> S(n) = d(n) / (I^n)<br /></pre><br />which I can use to restate the recurrence as:<br /><pre><br /> S(0) = d(0) / (I^0) = d(0) (since anything to the zeroth power is 1)<br /> S(n) = S(n-1) - P/(I^(n-1)) for n > 0<br /></pre><br />And now <span style="font-weight: bold;">S(n)</span> is, structurally, simple enough to be treated as a straight summation rather than a wacky recurrence:<br /><pre><br /> S(n) = d(0) - sum across k from 1 to n of: P/(I^(k-1))<br /><br /> = d(0) - P * sum across k from 0 to (n-1) of: (1/I)^k<br /><br /> = d(0) - that horrendous sum<br /></pre><br /><br />But it turns out that that horrendous sum is actually not bad at all. If we squint our eyes at it, it's a geometric sum, and those have a very nice closed form: it's the "first term - the first excluded term in the sum, divided by one minus the term ratio". That is, if I'm doing:<br /><pre><br /> a^0 + a^1 + a^2 + ... + a^n<br /></pre><br />I don't have to actually add up all those terms: I can just do:<br /><pre><br /> ((a^0) - a^(n+1)) / (1 - a)<br /></pre><br />So by applying the transformation on that geometric sum, I can get a closed form for <span style="font-weight: bold;">S(n)</span>:<br /><pre><br /> S(n) = d(0) - P * sum across k from 0 to (n-1) of: (1/I)^k<br /> = d(0) - P*(1 - (1/I)^n)/(1 - 1/I)<br /></pre><br /><br />But <span style="font-weight: bold;">S(n)</span> isn't what I was originally going after: I was going after <span style="font-weight: bold;">d(n)</span>, so let me backpatch that fix in:<br /><pre><br /> S(n) = d(n) / (I^n), so<br /> d(n) = S(n) * I^n<br /></pre><br />That is, my car debt at any given month is going to be:<br /><pre><br /> d(n) = I^n * (d(0) - P*(1 - (1/I)^n)/(1 - 1/I))<br /></pre><br /><br />Whew! Let's try that out:<br /><pre><br />(module car-payment-2 mzscheme<br /> ;; debt: number number number number -> number<br /> ;; Returns the amount of money owed after months pass,<br /> ;; given a particular monthly payment and apr rate.<br /> (define (debt months initial-debt monthly-payment apr)<br /> <br /> ;; closed-form: number number number number -> number<br /> ;; This is a closed form solution for the recurrence:<br /> ;;<br /> ;; d(n) = I * (d(n-1) - P)<br /> ;;<br /> ;; d(n) = I^n * (d(0) - P*(1 - (1/I)^n)/(1 - 1/I))<br /> (define (closed-solution n d0 P I)<br /> (let* ([I^n (expt I n)]<br /> [1/I (/ 1 I)]<br /> [1/I^n (/ 1 I^n)])<br /> (I^n . * .<br /> (d0 . - .<br /> ((P . * . (1 . - . 1/I^n))<br /> . / .<br /> (1 . - . 1/I))))))<br /> <br /> (let ([monthly-interest (+ 1 (/ apr 12))])<br /> (closed-solution months<br /> initial-debt<br /> monthly-payment<br /> monthly-interest))))<br /></pre><br />and let's look at a few values:<br /><pre><br />> (debt 0 16300 327.93 0.0767)<br />16300<br />> (debt 1 16300 327.93 0.0767)<br />16074.158147416665<br />> (debt 2 16300 327.93 0.0767)<br />15846.872788992236<br />> (map (lambda (x) (debt x 16300 327.93 0.0767))<br /> (list 0 1 2 3 4 5 6 7 8 9 10))<br />(16300<br />16074.158147416665<br />15846.872788992236<br />15618.13469831854<br />15387.934590015295<br />15156.263119353142<br />14923.110881874338<br />14688.46841301098<br />14452.32618770081<br />14214.67462000053<br />13975.5040626967)<br />> (map (lambda (x) (debt x 16300 327.93 0.0767))<br /> (list 56 57 58 59 60))<br />(1150.9196104211048<br />828.2498856810436<br />503.5177636170277<br />176.71006207281036<br />-152.1864853637734)<br /></pre><br /><br />I did a little fist-pump after I got this working. CS students are weird.Danny Yoohttp://www.blogger.com/profile/04298793721597988477noreply@blogger.com0