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))])
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.