Search code examples
schemeracket

A let in a cond in scheme possible?


I have a problem I don't understand how let works in a cond, with an if I understand it but not with a cond...

Here's my code with a let:

(define add2
  (lambda (l) 
    (cond ((null? l) l)
     (let ((R (add2 (cdr l))))
       ((list? (car l)) (cons (add2 (car l)) R))
       ((number? (car l)) (cons (+ 2 (car l)) R))
       (else (cons (car l) R))))))

When I try it on my list, it doesn't work.

Normally when I give my list:

(add2 '(2 b (10 a (56 3) 5) 4)) 

It should give me this :

(4 b (12 a (58 5) 7) 6))

It doesn't... It says: let: bad syntax

I think it might be this but I checked every parentheses and didn't see something missing...

But when I remove my let and try without it, it works!

(define add2
  (lambda (l) 
    (cond ((null? l) l)
      ((list? (car l)) (cons (add2 (car l)) (add2 (cdr l))))
      ((number? (car l)) (cons (+ 2 (car l)) (add2 (cdr l))))
      (else (cons (car l) (add2 (cdr l)))))))

(add2 '(2 b (10 a (56 3) 5) 4)) into (4 b (12 a (58 5) 7) 6))

Therefore I don't understand why it doesn't work and I would appreciate any help!


Solution

  • You cannot make your own syntax - but you have to follow the cond syntax which says:

    (cond ((condition-1 consequent-1)
           (condition-2 consequent-2)
           ...
           (else final-consequent)))
    

    So there is no room for a let which you can place into it.

    Recapitulating what you want actually

    You want to add 2 to only the numeric elements in a nested. Such recursive functions dealing with nested lists (trees) traditionally have a * at their end of their name.

    And let this function be tail-recursive. We would define it first without let:

    (define (add2* l (acc '())) 
      (cond ((null? l) (reverse acc))
            ((list? (car l)) (cons (add2* (car l)) (add2* (cdr l))))
            ((number? (car l)) (cons (+ 2 (car l)) (add2* (cdr l))))
            (else (cons (car l) (add2* (cdr l))))))
    

    Using let, you want to save some typing. Do it by nesting two cond expressions:

    (define (add2* l (acc '())) 
      (cond ((null? l) (reverse acc))
            (else (let ((R (add2* (cdr l))))
                    (cond ((list? (car l)) (cons (add2* (car l)) R))
                          ((number? (car l)) (cons (+ 2 (car l)) R))
                          (else (cons (car l) R)))))))
    

    You could use if for the outer cond:

    (define add2
      (lambda (l) 
        (if (null? l) 
            l
            (let ((R (add2 (cdr l))))
              (cond ((list? (car l)) (cons (add2 (car l)) R))
                    ((number? (car l)) (cons (+ 2 (car l)) R))
                    (else (cons (car l) R)))))))
    

    Whish is the same like:

    (define (add2 l) 
      (if (null? l) 
          l
          (let ((R (add2 (cdr l))))
            (cond ((list? (car l)) (cons (add2 (car l)) R))
                  ((number? (car l)) (cons (+ 2 (car l)) R))
                  (else (cons (car l) R))))))
    

    Generalization by map-tree

    This thing is actually a map-tree:

    (define (map-tree func tree (acc '()))
      (cond ((null? tree) (reverse acc))
            ((list? (car tree)) (cons (map-tree func (car tree)) (map-tree func (cdr tree))))
            (else (cons (func (car tree)) (map-tree func (cdr tree))))))
    

    And using this more general function, you define your add2* by:

    (define (add2 x)
      (if (number? x) 
          (+ 2 x) 
          x))
    
    (define (add2* tree)
      (map-tree add2 tree))
    

    And test it:

    (add2* '(2 b (10 a (56 3) 5) 4))                                              
    ;; => '(4 b (12 a (58 5) 7) 6)
    

    Generalization by map-tree makes it possible for you to define other kind of such similar functions - since it decouples the actual function applied to each element of the tree from the traversal and application of the function on the tree.

    e.g. you could define a add5* easily:

    (define (add5 x)
      (if (number? x)
          (+ 5 x)
          x))
    
    (define (add5* tree)
      (map-tree add5 tree))
    

    Generalize definition of addN and addN*

    Since this also follows some pattern, you can let a function return the corresponding add7 and add7*:

    (define (generate-addN n)
      (lambda (x)
        (if (number? x)
            (+ n x)
            x)))
    
    (define (generate-addN* n)
      (lambda (tree)
        (map-tree (generate-addN n) tree)))
    

    These functions generate you the functions for addN and addN*:

    (define add7 (generate-addN 7))
    
    (define add7* (generate-addN* 7))
    

    Or write a general addN* function

    Or you directly define and use a generalized addN* function which takes n and the tree as its arguments:

    (define (addN* n tree)
      (map-tree (generate-addN n) tree))
    

    or write-out the generate-addN as a lambda expression:

    (define (addN* n tree)
      (map-tree (lambda (x) 
                  (if (number? x) 
                      (+ n x) 
                      x)) 
                tree))
    

    And use it

    (addN* 7 '(2 b (10 a (56 3) 5) 4))
    ;; => '(9 b (17 a (63 10) 12) 11)
    

    Other Use

    You could also define a function which recursively adds at the and of a string element the suffix: "_s".

    (define (addS* s tree)
      (map-tree (lambda (x)
                  (if (string? x)
                      (~a x s)
                      x))
                tree))
    
    (addS* "_s" '(2 b (10 a ("abc" 3) "def") 4))
    ;; => '(2 b (10 a ("abc_s" 3) "def_s") 4)