How to use `splicing-syntax-parameterize`?

The form (syntax-parameterize ([id expr] ...) body-expr ...+) binds the ids during the expansion of the body expressions.

I am looking for something that allows be to do the same, but can be used in an internal definition context.

My experiments with splicing-syntax-parameterize has however not been succesful.

What am I missing in the example below?

#lang racket
(require racket/stxparam
         racket/splicing
         (for-syntax racket/syntax))

; SYNTAX (context)
;  Expands to the either 'module-level or 'local
(define-syntax-parameter context (λ (stx) #''module-level))

; SYNTAX (local)
;  Change the context to 'local.
(define-syntax (local stx)
  (with-syntax ([context (format-id stx "context")])
    #'(splicing-syntax-parameterize
          ([context (λ (stx) #''local)]))))

;;; Test Program

(context)    ; -> 'module  OK

(let ()
  (local)    ; this is intended to rebind the `context` on the next line
  (context)) ; <- and make this call to return 'local

(context)    ; -> 'module  OK

The program currently prints:

'module-level
'module-level
'module-level

Splicing forms don’t do what you think they do—they are intended for the splicing of their body forms. I don’t think a definition-like syntax parameterization is possible without explicitly controling the expansion of the whole context (which is what Rhombus does to implement syntax_parameter.relet).

1 Like

You have no body for splicing-syntax-parameterize. You have roughly:

(context)
(let ()
  (splicing-syntax-parameterize ([context …])
    )
(context))

The form splices its body, not its parameterization. Does that at least help explain why it won't work as you wrote it?

1 Like

One other idea: make your macro an internal-definition context:

(define-syntax (local stx)
  (with-syntax ([context (format-id stx "context")]) ;; not sure what this is for?
    (syntax-parse stx
      [(_ e ...+) #'(splicing-syntax-parameterize ([context (λ (stx) #''local)])
                      e ...)])))

(local
 (define foo (context))
 foo)
1 Like

@usao and @benknoble
Yeah, only the body is spliced. I was led astray by the comment in the docs, that the body could be empty. The answer is to use define-syntax-parameter in the body of one of the splicing forms.

#lang racket
(require racket/stxparam
         racket/splicing
         (for-syntax racket/syntax))

; SYNTAX (context)
;  Expands to the either 'module-level or 'local
(define-syntax-parameter context (λ (stx) #''module-level))

; SYNTAX (local)
;  Change the context to 'local.
(define-syntax (local stx)
  (with-syntax ([context (format-id stx "context")])
    #'(splicing-let ()
        (define-syntax-parameter context (λ (stx) #''local)))))

;;; Test Program

(context)    

(let ()
  (local)    
  (context)) 

(context)    

That solution requires the (context) to be nested inside the local form
and I wanted to avoid that for my use.

I am planning to make a form (declare-variables id ...) form that declares that the identifiers can appear as variables in linear equation. Another form (equation <equation>) that introduces a linear relation betweem the variables. And finally a reference to an identifier declared as a linear relation variable, will solve the equations in order to find its value.

Example:

(declare-vars a b)
(equation (= (+ a b) 10))
(equation (= b 4))
a  ; => evaluates to 6

To resolve a reference to a, we need to know all declared linear relation variables (and they respect local scope). The idea is thus to make declare-vars store the currently defined linear variables using a syntax parameter. My problem was that I could not the the syntax parameter to work in a local context. I think, I am now on the right track.

For something like this, I would not use a syntax parameter that way.

Instead, you could have (declare-variables id ...) expand to (begin (define-syntax id (make-variable)) ...) and use syntax-local-value (or the static syntax class) to recognize identifiers bound as variables. Then all handling of scope is taken care of for you by Racket.

Depending on the complexity of your project, you might also take a look at the ee-lib library, which supports writing macro-extensible DSLs. I still haven't gotten around to using the library for anything large, but I have implemented a similar approach by hand based on Alexis' blog posts before the library and paper existed.

1 Like

The problem is that when a variable is referenced, its value depends on the other variables in scope at the place of reference. Keeping track of the current set of variables in scope (or the current set of equations) needs a bit more.

This looks interesting. Thanks for the pointer.

What does the ee stand for. My thoughts went to "electrical engineering".

I think ee stands for “Embedded Expanders.”

1 Like

The following example defines to forms declare and variables.

The form declare is used to declare identifiers (to have some property).

The form variables expands to a list of all declared variables in scope,
where the form appears.

The form declare can occur multiple times in the same scope.

Suggestions?

If I could use the same expansion for declare for all applications
in the same scope, the definition of declare could be simplified.
That ties in with the original question: Is there an "syntax-parameterize"
suited for an internal definition context?

#lang racket
(require racket/stxparam racket/splicing)
(require (for-syntax racket/base syntax/parse racket/syntax syntax/stx))
(require (for-syntax (for-syntax syntax/parse racket/base)))

;;; UTILITIES

(begin-for-syntax
  ; SYNTAX (push*! id expr)
  ;   The result of `expr` must be a list of identifiers.
  ;   The identifiers pushed onto the list held by `id`.
  (define-syntax (push*! stx)
    (syntax-parse stx [(_ id:id e:expr)
                       #'(let ([vs e])
                           (for ([v (in-list (stx->list vs))])
                             (set! id (cons v id))))])))

;;; DECLARED VARIABLES

(begin-for-syntax
  ;; Module Level Variables
  (define module-level-declared-variables '()) ; list of identifiers
  (define (add-module-level-variables! ids)
    (push*! module-level-declared-variables (stx->list ids)))
  ;; Local Variables
  ;; - each internal definition context is given an index
  (define intdefs (make-hasheq))
  ;; - each index is associated to a list of declared names
  (define local-variables (make-hash))
  
  (define (new-intdef? intdef)          (not (hash-has-key? intdefs intdef)))
  (define (index-of-intdef intdef)      (hash-ref! intdefs intdef (hash-count intdefs)))
  (define (get-local-variables index)   (hash-ref local-variables index))
  (define (add-local-variable! index id)
    (hash-update! local-variables index (λ (old-vars) (cons id old-vars)) '()))
  (define (add-local-variables! index vars)
    (for ([id (stx->list vars)]) (add-local-variable! index id))))

;; SYNTAX PARAMETER  variables
;;   When applied expands to a list with the declared variables in scope.
(define-syntax-parameter variables
  (λ (stx) (with-syntax ([(id ...) module-level-declared-variables])
             #''(id ...))))

;; SYNTAX (declare id ...)
;;   Declare that the variables id ... are "declared" variables.
(define-syntax (declare stx)
  (syntax-parse stx
    [(_declare id ...)
     ; - the expansion on `declare` depends on where it is used.
     (define ctx (syntax-local-context))
     (case ctx 
       [(module)
        (add-module-level-variables! #'(id ...))
        #'(begin)]
       [(expression)
        (raise-syntax-error 'declare "not allowed in an expression context" stx)]
       [else ; local context
        ; - find the local variables in outside this scope
        (define vars (local-expand
                      (with-syntax ([variables (syntax-local-get-shadower #'variables)])
                        #'(variables))
                      ctx '()))
        ; - if we are in a new scope, we must reparameterize `variables`
        ; - if not simply add the new identifiers 
        (define new-scope? (new-intdef? ctx))
        (define index      (index-of-intdef ctx))
        (cond
          [new-scope?
           (add-local-variables! index #'(id ...))
           (with-syntax ([variables               (format-id stx "variables")]
                         [(_quote (outer-id ...)) vars]
                         [index                   index]
                         [....                     #'(... ...)])
             #'(define-syntax-parameter variables
                 (λ (stx)
                   (with-syntax ([(local-id ....) (get-local-variables index)])
                     #''(local-id .... outer-id ...)))))]
          [else
           (add-local-variables! index #'(id ...))
           #'(begin)])])]))
        

(variables)                   ;                    ()
(declare a b)
(variables)                   ;             (a b    )
(let ()
  (declare c d)  
  (displayln (variables))     ; (    i j c d a b g h)
  (let ()
    (declare e f)
    (displayln (variables)))  ; (e f i j c d a b g h)
  (declare i j)
  (displayln (variables)))    ;     (i j c d a b g h)
(variables)                   ;             (a b    )
(declare g h)
(variables)                   ;             (a b g h)