0
votes

I've just started learning Racket.

I have written this procedure:

#lang racket

(define split
   (lambda (list)
      (define plus-list '())
      (define minus-list '())

      (cond ((null? list) '())
            (else
               (do ([i (length list) (- i 1)])
                   ((zero? i))
                   (define l (list-ref list i))
                   (define item (last-element-on-list l))

                   (cond ((= (cdr l '+)) (set! plus-list (cons list plus-list)))
                         ((= (cdr l '-)) (set! minus-list (cons list minus-list))))
               )

               (cons plus-list minus-list)
            )
      )
   )
)

And instead of using (list-ref lst i) inside de do I have defined a variable l:

(define (list-ref lst i) l)

But it seems that I cann't do that, because I get the error:

define: not allowed in an expression context in: (define l (list-ref lst i))

But there are a lot of define inside the do.

If I remove all the define inside the do, I have to write a lot of code and it is not easier to read and understand:

(define split
   (lambda (list)
      (define plus-list '())
      (define minus-list '())

      (cond ((null? list) '())
            (else
               (do ([i (length list) (- i 1)])
                   ((zero? i))

                   (cond ((= (cdr (last-element-on-list (list-ref list i)) '+)) (set! plus-list (cons (list-ref list i) plus-list)))
                         ((= (cdr (last-element-on-list (list-ref list i)) '-)) (set! minus-list (cons (list-ref list i) minus-list))))
               )

               (cons plus-list minus-list)
            )
      )
   )
)

How can I define a variable inside a do?

3

3 Answers

1
votes

Reading your other question I see why you write the bolded expressions -

…

(cond ((= (cdr (last-element-on-list (list-ref list i)) '+))
       (set! plus-list
             (cons (list-ref list i) plus-list)))

      ((= (cdr (last-element-on-list (list-ref list i)) '-))
       (set! minus-list
             (cons (list-ref list i) minus-list))))
…

Your input list shown there is –

(define lst
  '((n 25 f +)
    (s 25 m +)
    (ll 20 no -)))

Your split is inspecting the contents of each element of l. split has overstepped its boundaries and now it only works for lists containing elements of this particular structure. Along with set!, lack of else in a cond is typically an indication you're doing something wrong. You also call (cdr (last-element-of-list ...)). If last-element-of-list returns an atom, cdr would throw an error here.

Consider designing split in a more generic way –

(define (split proc l)
  (define (loop l true false)
    (cond ((null? l)
           (cons true false))
          ((proc (car l))
           (loop (cdr l)
                 (cons (car l) true)
                 false))
          (else
           (loop (cdr l)
                 true
                 (cons (car l) false)))))
  (loop l '() '()))

(split (lambda (x) (> x 5))
       '(1 5 3 9 7 0 8 3 2 6 4))

;; '((6 8 7 9) 4 2 3 0 3 5 1)

If our list contains different elements, we can still use the same split procedure –

(split (lambda (x) (eq? '+ (cadr x)))
       '((1 +) (1 -) (2 +) (3 +) (2 -) (3 -) (4 +)))
;; '(((4 +) (3 +) (2 +) (1 +)) (3 -) (2 -) (1 -))

I think it's never too early to start learning continuation passing style. Below, return represents our continuation and defaults to cons, the same procedure we used to return the final result in our original implementation. Intuitively, a continuation represents "the next step" of the computation –

(define (split proc l (return cons)) ;; `return` is our continuation
  (if (null? l)
      ;; base case: list is empty, return empty result
      (return '() '())
      ;; inductive case: at least one `x`
      (let* ((x (car l))
             (bool (proc x)))
        (split proc          ;; tail recur with our proc
               (cdr l)       ;; ... a smaller list
               (lambda (t f) ;; ... and "the next step"
                 (if bool                        ;; if `(proc x)` returned true
                     (return (cons x t)          ;; ... cons the `x` onto the `t` result
                             f)                  ;; ... and leave the `f` the same
                     (return t                   ;; otherwise leave `t` the same
                             (cons x f))))))))   ;; ... and cons the `x` onto the `f` result

If we run our split procedure, you'll notice we get the same exact output as above. At first glance it looks like we made a mess of a nice program, however there's one distinct advantage of this implementation. Because the continuation is user-configurable, instead of cons, we could decide an entirely different fate for our two lists, t and f

(split (lambda (x) (eq? '+ (cadr x)))
       '((1 +) (1 -) (2 +) (3 +) (2 -) (3 -) (4 +))
       (lambda (plus minus)
          (printf "plus: ~a, minus: ~a\n" plus minus)))
;; plus: ((1 +) (2 +) (3 +) (4 +)), minus: ((1 -) (2 -) (3 -))

Note how plus and minus were given the respective results. We didn't have to pick apart an intermediate cons result. More intuitively, we want printf to be "the next step", but we only need to specify the first argument –

(split (lambda (x) (eq? '+ (cadr x)))
       '((1 +) (1 -) (2 +) (3 +) (2 -) (3 -) (4 +))
       (curry printf "plus: ~a, minus: ~a\n"))
;; plus: ((1 +) (2 +) (3 +) (4 +)), minus: ((1 -) (2 -) (3 -))

Now we've scratched the surface of functional style :D

1
votes

do loops are not idiomatic Racket. They are inherited from Scheme, and for whatever reason, they don’t permit internal definitions. I have never once used a do loop in Racket, since the for comprehensions are more functional, and they’re just generally easier to work with. Plus, since they originate in Racket, not in Scheme, they support internal definitions as you’d expect.

You could write your split function using for/fold instead of do, which has the added advantage of not needing to use set! (and avoiding the quadratic access time of using list-ref instead of iterating through the list). I’m not completely sure what your split function is supposed to do, as even with the internal definition removed, it does not compile, but here’s my best guess at what you might be attempting to do:

(define (split lst)
  (for/fold ([plus-lst '()]
             [minus-lst '()])
            ([l (in-list lst)])
    (define item (last l))
    (cond
      [(equal? item '+)
       (values (cons l plus-lst) minus-lst)]
      [(equal? item '-)
       (values plus-lst (cons l minus-lst))]
      [else
       (values plus-lst minus-lst)])))

Aside from the obvious restructuring to use for/fold instead of do, this code also makes the following changes over your code:

  1. It uses the built-in last function from racket/list to get the last element of a list.

  2. It uses equal? instead of = to compare symbols, since = is specifically for comparing numbers.

  3. It indents things properly and puts close parentheses in idiomatic locations.

-1
votes

I fixed your code using let, read documentation about let it's heavily used in Scheme/Racket. I haven't used Scheme lately so I couldn't explain it as well as it is in documentation.

Shortly it's local symbol definition/redefinition, and you can use symbol with value only in let body.

Short example on let

(define x 5)

(let ((x 10))
  (display x)) # => 10

(display x) # => 5

(let ((y 1))
  (display y)) # => 1

(display y) # = => (error)  y: undefined

Your code fixed using let

(define split
(lambda (list)
 (let ((plus-list '())
        (minus-list '()))

  (cond ((null? list) '())
        (else
           (do ([i (length list) (- i 1)])
               ((zero? i))
               (let ((l (list-ref list i))
                     (item (last-element-on-list l)))

               (cond ((= (cdr l '+)) (set! plus-list (cons list plus-list)))
                     ((= (cdr l '-)) (set! minus-list (cons list minus-list))))))
           (cons plus-list minus-list))))))