2
votes

I'm almost embarrassed for asking this Racket/Scheme question, but can anybody tell me how to avoid repeating a function call inside "there" if it was used to determine the condition "here" in the first place? (cond [here there])

What I am trying to get is the equivalent of the following C style code in Racket/Scheme (notice that I only had to call regex() once, since it was stored in a variable match):

// initialiation code
if (match = regex("start", "startofstring")) {
  list->push(match);
} 
else {
  printf("No matching regex\n");
}

The Racket code that I want to avoid is the following since I have to call regexp-match twice:

(cond
  [(regexp-match #rx"start" "startofstring")
    (set! list (cons (regexp-match #rx"start" "startofstring") list)]
  [else
    (printf "No matching regex\n")])

Now, I could do:

(define match (regexp-match #rx"start" "startofstring"))
(cond
  [match
    (set! list (cons match list)]
  [else
    (printf "No matching regex\n")])

But this approach will mean that I have to define very many variable if I have more than one condition (in my actual code, I have more than one condition... but for the sake of the snippet above, I only put in one). So it will end up looking ugly like this:

(define match1 (regexp-match #rx"start" "startofstring"))
(define match2 (regexp-match #rx"blah" "startofstring"))
....
(define matchn (regexp-match #rx"blahn" "startofstring"))
(cond
  [match1
    (set! list (cons match1 list)]
  [match2
    (set! list (cons match2 list)]
  ....
  [matchn
    (set! list (cons matchn list)]
  [else
    (printf "No matching regex\n")])

What I would like is something more along the lines of:

(cond
  [(define match (regexp-match #rx"start" "startofstring"))
    (set! list (cons match list)]
  [(define match (regexp-match #rx"blah" "startofstring"))
    (set! list (cons match list)]
  ...
  [(define match (regexp-match #rx"blahn" "startofstring"))
    (set! list (cons match list)]
  [else
    (printf "No matching regex\n")])

But this is obviously a syntax error because (define .. ..) can't be used in the condition "here". Sorry for the lack of clarity... I tried the best I could to convey what I am saying. I know this is very super simple but I can't quite wrap my head around it (I haven't use languages other than c-style languages).

2

2 Answers

5
votes

The correct solution is to use cond's => form:

(cond ((regexp-match #rx"start" "startofstring")
       => (lambda (match)
            (set! lst (cons match lst))))
      ...)

(Note that I renamed your list variable to lst to avoid shadowing the built-in list procedure.)

If you have many patterns, and the same action for multiple patterns, you should extract out the common code into a separate procedure:

(define (push! match)
  (set! lst (cons match lst)))
(cond ((regexp-match #rx"start" str) => push!)
      ((regexp-match #rx"blah" str) => push!)
      ...)

While the above works, I want to suggest that you don't use set!, since it's not very functional. The standard Scheme way to build up a list from a loop is to use a named let, like so:

(let loop ((result '())
           (strs strs))
  (define (next match)
    (loop (cons match result) (cdr strs)))
  (if (null? strs)
      result
      (let ((str (car strs)))
        (cond ((regexp-match #rx"start" str) => next)
              ...)))
2
votes

There are a couple of clean ways to do this. First of all, you could use match here.

(match "startofstring"
  [(regexp #rx"start" match) (cons match list)]
  [(regexp #rx"blah" match) (cons match list)]
  ...
  [_ (printf "No matching regex.\n")])

Of course, even that has a lot of repetition. You could do this a little differently with a small helper function.

(define (any-match str . regexes)
  (for/or ([regex (in-list regexes)])
    (regexp-match regex str)))

(let ([match (any-match "startofstring"
                        #rx"start"
                        #rx"blah")])
  (if match
      (cons match list)
      (printf "No matching regex\n")))

Alternatively, if you don't want the baggage of a helper function, but you're okay with a little bit more repetition, you can just do this:

(let ([match (or (regexp-match #rx"start" "startofstring")
                 (regexp-match #rx"blah" "startofstring"))])
  (if match
      (cons match list)
      (printf "No matching regex\n")))