Search code examples
schemeracketlet

Not able to use let in Racket


I am trying a modification of sort code on http://home.adelphi.edu/~siegfried/cs270/270rl10.html where I am using let for the insert function:

(define (mysort alon ) 
  (let insert ((n n) (alon  alon)) 
    (cond 
      [(empty? alon) (cons n empty)] 
      [else (cond 
              [(< n (first alon)) (cons n alon)] 
              [else (cons (first alon) 
                          (insert n (rest alon))])])
  (cond 
    [(empty? alon) empty] 
    [(cons? alon) (insert (first alon) 
                           (mysort (rest alon)))])))

(mysort (list 1 2 3 4 5 6 2 3 1 4 5 2 10))

However, it is not working at level of 'let' variable declaration :

n: unbound identifier in module in: n

I see here (https://docs.racket-lang.org/reference/let.html) that 'let' needs to have initial values of variables. Can we use 'let' without initializing the variables? How can above code be corrected?


Edit: I tried to use lambda but it does not work:

(define (mysort4 alon ) 
  (let ([insert4
         (lambda (n alon) 
           (cond 
             [(empty? alon) (cons n empty)]
             [(< n (first alon)) (cons n alon)] 
             [else (cons (first alon) 
                         (insert4 n (rest alon) ))]))])
    (cond 
      [(empty? alon) empty] 
      [(cons? alon) (insert4 (first alon) 
                             (mysort4 (rest alon) ) )])))

(mysort4 (list 1 2 3 4 5 6 2 3 1 4 5 2 10))

The error is:

insert4: unbound identifier in module in: insert4

Solution

  • When you create something with let

    (define test 10)
    
    (let ((test (lambda (x) 
                  (list x test))))
      (test 'result))
    ; ==> (result 10)
    

    Obviously test inside the lambda is the global one and not "itself", but why. let is just syntax sugar for an anonymous function that is immediately called so we can rewrite it to this:

    (define test 10)
    ((lambda (test)
       (test 'result))
     (lambda (x) 
       (list x test)))
    

    Here you see that the first lambda has test as a bound variable and thus always will be the first operand, but the second lambda doesn't have test except for the global binding.

    In recursive procedures one cannot use let because the bindings are not in the environment when the closure is created (lambda is evaluated). To fix this we use letrec which fixes this problem:

    (define test 10)    
    (letrec ((test (lambda (x) 
                  (list x test))))
      (test 'result))
    ; ==> (result #<procedure:test>)
    

    To see why you can look at the expansion of this form:

    (let ((test 'undefined)) 
      (let ((newtemp (lambda (x) (list x test)))) 
        (set! test newtemp)) 
      (test 'result))))
    ; ==> (result #<procedure:test>)
    

    I won't expand the let forms here, but look at the fact that test exists at the time the lambda is created. The lambda becomes the value but the binding is the same.

    define that is not top level is rewritten* to a letrec so the code below is exactly the same:

    (let () ; this is to make `define` local and not global
      (define test (lambda (x) 
                     (list x test))
      (test 10))
    ; ==> (result #<procedure:test>)
    

    In a named let the name is bound in a letrec, but the other values are not. To fix the first:

    (define (mysort alon)   
      (cond 
        [(empty? alon) empty] 
        [(cons? alon)
         (let insert ((n (first alon))
                      (alon  (rest alon))) 
           (cond 
             [(empty? alon) (cons n empty)] 
             [(< n (first alon)) (cons n alon)] 
             [else (cons (first alon) (insert n (rest alon)))]))]))
    

    Notice that I flattened the nested cond since the whole point of cond is to not have to nest them. It's the equivalent to if-elseif*-else in other languages. The code doesn't work since it only places the first element. Pehaps inserting all elements in a list starting as an empty list would work.

    The second would work with just changing from let to letrec. The code is identical with the same feature that it only inserts the first element according to the rest, but the recursion will work for that one element.

    If you look at the page you linked to you'll see that the list you insert into is already sorted. ie. you are missing something there..

    insertion-sort is not an efficient algorithm. #lang racket uses merge sort with tweaks for smaller lists. Trying to beat it on speed would be a fools errand.

    * or vice versa. racket uses letrec (letrec-values to be precise)