Question

My question is about rewriting a nested if conditions to a single cond with a branch having a local binding. I am very new to Racket, just making my first steps, so if my question is stupid, please, be lenient.

In brief the task is to write a function that takes a vector and searches a value in it. The vector contains mixed stuff -- pairs and non-pairs. The value of interest should be in the car of a pair.

The working solution uses a recursive helper function with nested ifs

[vlen (vector-length vec)]
[find-in-vector
(lambda (pos)
 (if (= pos vlen)                               ;; if the end of the vector has been reached
     #f                                         ;; then return false
     (let ([el (vector-ref vec pos)])           ;; Otherwise, extract current element from the vector,
       (if (and (pair? el) (equal? v (car el))) ;; if the element is a pair and its car is what we want
           el                                   ;; then return the element
           (find-in-vector (+ 1 pos))))))]      ;; otherwise keep searching the vector

I would like to rewrite it so that it uses cond that looks more compact. The below code is a possible implementation. The problem is that (vector-ref vec pos) is computed several times and this is what I would like to rewrite such that it is computed only once, like in the previous implementation with nested ifs

[vlen (vector-length vec)]
[find-in-vector
 (lambda (pos)
   (cond [(= pos vlen) #f]
         [(and (pair? (vector-ref vec pos))            ;; one
               (equal? v (car (vector-ref vec pos))))  ;; two
          (vector-ref vec pos)]                        ;; three is too many
         [#t (find-in-vector (+ 1 pos))]))])

And this is what I achieved at most: one call to (vector-ref vec pos) in test-expr and another call in result-expr

(cond
  [(= pos vlen) #f]
  [(letrec ([el (vector-ref vec pos)])      ;; extract current element from the vector
     (and (pair? el) (equal? v (car el))))  ;; and use it in conditionals
   (vector-ref vec pos)]                    ;; again, extract and return. FIXIT
  [#t (find-in-vector (+ 1 pos))]))])       ;; otherwise, keep searching

How can I further make el shared between the test-expr and result-expression? And I would like el to remain local to this particular cond-branch. The below code works incorrectly. AFAIU, the whole letrec expression is viewed as a text-expr of the cond?

(cond
 [(= pos vlen) #f]
 [(letrec ([el (vector-ref vec pos)])
    (and (pair? el) (equal? v (car el)))
    el)]
 [#t (find-in-vector (+ 1 pos))])
Was it helpful?

Solution

You can do it if you import SRFI 61 first:

(require srfi/61)
(define (find-in-vector vec v)
  (define vlen (vector-length vec))
  (let loop ((pos 0))
    (cond
      ((= pos vlen) #f)
      ((vector-ref vec pos)
       (lambda (el) (and (pair? el) (equal? v (car el))))
       => values)
      (else (loop (add1 pos))))))

The important thing that SRFI 61 provides is that it allows for the (<generator> <guard> => <receiver>) clause. Here, the generator is the thing that creates a common value to be used by both the guard and the receiver. Our receiver in this case is simply values, which returns the value it's given without any processing.


Update: Currently srfi/61 does not work correctly for programs that use #lang racket or the like (srfi/61 is using different bindings for => and else from what racket/private/cond provides). This has been recently fixed and should appear in a future Racket release.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top