Execute thrice only

Let's say I want a particular piece of code to execute at most three times within the execution of a larger program.

The obvious code is something like

(define globalcounter 3)

and where I have the piece of code I wrap it in

(if ( > globalcounter 0) 
  (begin (piece-of-code) (set! globalcounter ( - globalcounter 1)))
  (complain)
)

I might want to have a macro.

  (atmostthrice foo)

would expand to

  (if ( > globalcounter 0) 
  (begin foo (set! globalcounter ( - globalcounter 1)))
  (complain)
)

Then I could use this macro if there were other things I wanted to restrict to excuting thrice:

(thrice dothis)

and elsewhere

(thrice dothat)

But this is wrong! (thrice dothis) and (thrice dothat) will end up sharing the same counter. If I dothis twice, I will no longer be able to count out three executions of dothat.

Somehow I need to generate a new global counter for each use of thrice.

I there any way of getting each static use of to a macro to generate a new initialized global variable?

I've thought of the following ugliness, but I don't know if it is defined to work:

(thrice foo)

could expand to

(define localglobal `,(mcons 3 #nil) )
(if ( > (mcar localglobal) 0)
     (begin foo (set-mcar! localglobal ( - (mcar localglobal) 1)
     (complain)
)

thereby embedding a mutable cell in the macro-expanded source code, but that seems unconscionably cute. I suspect it would fail in an implementation where compilation is separated from execution.

-- hendrik

Your macro can use syntax-local-lift-expression to ask the expander to create a fresh module-level variable for you:

#lang racket

(require syntax/parse/define)

(define-syntax-parse-rule (atmostthrice body:expr ...+)
  #:with globalcounter (syntax-local-lift-expression #'3)
  (cond
    [(= 0 globalcounter)
     (error "too many times")]
    [else
     (set! globalcounter (sub1 globalcounter))
     body ...]))

(define (try-it)
  (atmostthrice
   (displayln "run"))
  (try-it))

(with-handlers ([exn:fail? values])
  (try-it))
(try-it)

Note that this version is not thread-safe in the sense that it may be interrupted between the check of the counter and the update. Here's a better version:

#lang racket

(require syntax/parse/define)

(define-syntax-parse-rule (atmostthrice body:expr ...+)
  #:with globalcounter (syntax-local-lift-expression #'(box 3))
  (let retry ()
    (define old (unbox* globalcounter))
    (cond
      [(= 0 old)
       (error "too many times")]
      [(box-cas! globalcounter old (sub1 old))
       body ...]
      [else
       (retry)])))

(define (try-it)
  (atmostthrice
   (displayln "run"))
  (try-it))

(with-handlers ([exn:fail? values])
  (try-it))
(try-it)
1 Like

For completeness, here's a version that uses a semaphore instead of an atomic box. You might prefer this if you expect there to be contention, because Racket guarantees:

Semaphore waiting is fair: if a thread is blocked on a semaphore and the semaphore’s internal value is non-zero infinitely often, then the thread is eventually unblocked.

(But surely you would not be doing this if you expected there to be contention!)

#lang racket

(require syntax/parse/define)

(define-syntax-parse-rule (atmostthrice body:expr ...+)
  #:with sema (syntax-local-lift-expression #'(make-semaphore 3))
  (sync/timeout (λ ()
                  (error "too many times"))
                (handle-evt sema
                            (λ (ignored)
                              body ...))))

(define (try-it)
  (atmostthrice
   (displayln "run"))
  (try-it))

(with-handlers ([exn:fail? values])
  (try-it))
(try-it)
1 Like

Your macro can use syntax-local-lift-expression to ask the expander to create a fresh module-level variable for you:

#lang racket

(require syntax/parse/define)

(define-syntax-parse-rule (atmostthrice body:expr ...+)
  #:with globalcounter (syntax-local-lift-expression #'3)

It seems that syntax-local-lift-expression does what I am asking for.

Where can I find documentation for #:with ?

(cond
[(= 0 globalcounter)
(error "too many times")]

I see -- the error breaks the recursion in try-it

It is one of the pattern directives supported by syntax-parse and related forms: the are documented in this section of the manual.