[racket] Rookie Question on Functional Languages

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Thu Dec 9 15:41:40 EST 2010

On 12/09/2010 12:48 PM, Luke Jordan wrote:
> I will look at for and in-range and spend some more time exploring the
> built-ins.  But I want to talk about the do-times solution, because that
> was the first thing I tried, but I couldn't make it work.  In chapter 11
> expression arguments are applied to something that builds up a result
> (as a list or an arithmetic operation).  I don't understand how that
> applies to what I'm trying to do here since I'm not building anything,
> just calling it over and over and ignoring the result.  Following what I
> learned from chapter 11 the code would look something like this, which
> doesn't make sense.
> (define (do-times n expr)
>    (cond
>      [(< n 1) evaluate-expr-last-time]
>      [else ?? evaluate-expr (do-times (sub1 n) expr)]))

Let me rewrite that a little bit:

;; do-times : Nat (-> Void) -> Void
;; Applies the given thunk n times.
(define (do-times n action)
   (cond [(zero? n) <BASE>]
          (<COMBINE> action (do-times (sub1 n) action)]))

What to put in the base case? Well, how many times should (do-times 0 
action) apply the action? None. So <BASE> shouldn't involve action at 
all. In Racket, the value that represents "nothing", "the commands have 
been executed", etc is called the void value---but unlike in C and Java, 
it actually is a value. You can get the void value by calling (void). 
Note that the only thing special about the void value is that the REPL 
printer doesn't print it; you could design this function using any value 
to represent "I'm done, nothing else to report".

So now how to <COMBINE>? It should perform the action (represented by a 
procedure of no arguments returning void) and then recur. The idiomatic 
Racket way is to use 'begin':

   (begin (action) (do-times (sub1 n) action))

But if you don't know 'begin', you can also do it using 'local' (or 
'let', etc):

   (local [(define do-not-care (action))]
     (do-times (sub1 n) action))

Finally, it is still important to test such functions. It's harder, but 
it's possible to test actions by their effect on data. (Mutation is 
covered in Part VII.) Here's one way:

;; test-do-times : Nat -> boolean
;; Should always return #t, if do-times is working.
(define (test-do-times n)
   (let ([counter 0])
     (begin (do-times n (lambda () (set! counter (add1 counter))))
            (= n counter))))

Or adapt that to your testing framework of choice (check-expect, 
rackunit, etc).


> On Thu, Dec 9, 2010 at 13:08, Ryan Culpepper <ryanc at ccs.neu.edu
> <mailto:ryanc at ccs.neu.edu>> wrote:
>     On 12/09/2010 11:51 AM, Luke Jordan wrote:
>           Here's a rookie question that stems from HtDP 29.3.2.
>         The idea is to test an expression a number of times while timing
>         it and
>         compare to another version of the same function.  The expression
>         finds a
>         route in a vector (graph) from node 0 to node 4.  The way I would do
>         this in C/Python is with a while loop, while the counter is not zero
>         execute the expression (ignore return value), and wrap it in the
>         timer.
>           In Racket I came up with:
>         (time (for-each
>                 (lambda (dest) (find-route 0 dest Graph))
>                 (make-list 1000 4)))
>         It seems unnecessarily tricky, but I couldn't think of a better
>         way.  Am
>         I missing a piece?  Does it only seem tricky because Racket is
>         my first
>         functional language.
>     To the student of functional programming:
>     You're right, it is unnecessary. You're using a list because you
>     know someone has already provided "loop" functions for lists. But
>     you just want to repeat an action N times---that's a natural number.
>     Refresh yourself on Section 11 (Natural Numbers) and design this
>     function:
>     ;; do-times : Nat (-> Void) -> Void
>     ;; Applies the given thunk N times.
>     To the aspiring Racketeer:
>     Racket has loop support for natural numbers, just not as a single
>     function like 'for-each'. Look at 'for' and 'in-range'.
>     Ryan

Posted on the users mailing list.