Schema macro called by a keyword that is not a list header

Suppose I want to call a Schema macro for something other than the first element in an s-expression. For example, suppose I wanted to replace define with the infix := style, so that:

 (a := 5) -> (define a 5) ((square x) := (* xx)) -> (define (square x) (* xx)) 

The actual conversion seems pretty simple. The trick will make Scheme find the := expressions and macro-expand them. I thought about the surrounding large sections of code that use the infix syntax with the standard macro, maybe: (with-infix-define expr1 expr2 ...) , and when the standard macro looks through the expressions in its body and performs any necessary conversions. I know that if I take this approach, I need to be careful to avoid converting lists, which should actually be data, such as quoted lists and some sections of quasi-list lists. An example of what I imagine:

 (with-infix-define ((make-adder n) := (lambda (m) (+ nm))) ((foo) := (add-3 := (make-adder 3)) (add-6 := (make-adder 6)) (let ((a 5) (b 6)) (+ (add-3 a) (add-6 b)))) (display (foo)) (display '(This := should not be transformed)) 

So my question is double:

  • If I take the with-infix-define route, do I need to keep track of any stumbling blocks other than quotation and quasicot?
  • I like a little bit that I reinvent the wheel. This type of code walker is similar to what standard macro extension systems should do - the only difference is that they only look at the first item on the list when deciding whether to make any code transformations. Is there a way that I can just contact existing systems?
+7
source share
2 answers
  • Before continuing this, it's best to think about it - IME, you would often find what you really need to read at the reader level := as an infix syntax. This, of course, means that it is also an infix in quotes, etc., so long as it seems bad, but again, my experience is that you will eventually realize that it’s better to do something sequentially.

  • For completeness, I mentioned that in Racket there is a hack to read-syntax for infix-like expressions: (x . define . 1) reads as (define x 1) . (And, as above, it works everywhere.)

  • Otherwise, your idea of ​​wrapping a macro is pretty much the only thing you can do. This does not make it completely hopeless, but you can have a hook in the implementation extender that can allow you to do such things - for example, Racket has a special macro called #%module-begin that wraps the complete module of the module and #%top-interaction , which wraps top-level expressions in a REPL. (Both of them are added implicitly in two contexts.) Here's an example (I use Racket define-syntax-rule for simplicity):

     #lang racket/base (provide (except-out (all-from-out racket/base) #%module-begin #%top-interaction) (rename-out [my-module-begin #%module-begin] [my-top-interaction #%top-interaction])) (define-syntax infix-def (syntax-rules (:= begin) [(_ (begin E ...)) (begin (infix-def E) ...)] [(_ (x := E ...)) (define x (infix-def E) ...)] [(_ E) E])) (define-syntax-rule (my-module-begin E ...) (#%module-begin (infix-def E) ...)) (define-syntax-rule (my-top-interaction . E) (#%top-interaction . (infix-def E))) 

    If I put this in a file called my-lang.rkt , now I can use it as follows:

     #lang s-exp "my-lang.rkt" (x := 10) ((fib n) := (done? := (<= n 1)) (if done? n (+ (fib (- n 1)) (fib (- n 2))))) (fib x) 
  • Yes, you need to deal with a bunch of things. Two examples from the above are processing begin statements and function processing. This is certainly a very incomplete list - you will also want the bodies of lambda , let , etc. But this is still better than some kind of blind massage, because it is simply impractical, because you cannot tell in advance how some random piece of code will end. As a simple example, consider this simple macro:

     (define-syntax-rule (track E) (begin (eprintf "Evaluating: ~s\n" 'E) E)) (x := 1) 
  • The result is that for the correct solution, you need to pre-deploy the code somehow, so that you can then scan it and deal with several well-known basic forms in your setup.

  • Yes, all this repeats the work that macro expanders do, but since you are changing how the extension works, there is no way around this. (To understand why this is a fundamental change, consider something like (if := 1) - is it a conditional expression or definition? How do you decide which one takes precedence?) For this reason, for languages ​​with such “nice syntax” more A popular approach is to read and parse the code into simple S-expressions, and then let the actual language implementation use simple functions and macros.

+12
source

Overriding define bit trickier. See @Eli for a great explanation.

If, on the other hand, you are happy with := , to use set! things are a little easier.

Here is a small example:

 #lang racket (module assignment racket (provide (rename-out [app #%app])) (define-syntax (app stx) (syntax-case stx (:=) [(_ id := expr) (identifier? #'id) (syntax/loc stx (set! id expr))] [(_ . more) (syntax/loc stx (#%app . more))]))) (require 'assignment) (define x 41) (x := (+ x 1)) (displayln x) 

To save the example in a single file, I used submodules (available in the preliminary version of Racket).

+3
source

All Articles