macrosschemechicken-schemesyntax-rules

Introducing a Named Variable with Syntax Rules


I am trying to write a super-tiny Object-oriented system with syntax-rules, mostly just to learn it. Anyway, I am trying to introduce a "this" variable. Here is what I would like to be able to do:

(oo-class Counter 
    (
        (attr value 0)
        (attr skip 1)
    )
    (
        (method (next) (set! value (+ value skip)) value)
        (method (nextnext) (this 'next) (this 'next))
        (method (set-value newval) (set! value newval))
        (method (set-skip newskip) (set! skip newskip))
    )
)

(define c (Counter))
((c 'set-value) 23)
((c 'next))
((c 'nextnext))

I can get everything to work except "this". It seems like syntax-rules doesn't allow variable introduction. I thought I could get it by defining it as one of the literals in syntax-rules, but this does not seem to work.

Below is my object-oriented system:

(define-syntax oo-class
    (syntax-rules (attr method this)
        (
            (oo-class class-name 
                ((attr attr-name initial-val) ...) 
                ((method (meth-name meth-arg ...) body ...) ...))
            (define class-name 
                (lambda () 
                    (letrec
                        (
                            (this #f)
                            (attr-name initial-val)
                            ...
                            (funcmap 
                                    (list 
                                        (cons (quote meth-name) (cons (lambda (meth-arg ...) body ...) '()))
                                        ...
                                    )
                            )
                        )
                        (set! this (lambda (methname)
                            (cadr (assoc methname funcmap))
                        ))
                        this
                    )
                )
            )
        )
    )
)

This works for everything except 'nextnext, which errors out when it tries to reference "this".

Is this the right way to do this? Is there some other way to do this? I recognize that this is slightly unhygienic, but isn't that at least part of the point of specifying literals?

I've tried this in Chicken Scheme as well as DrRacket in R5RS mode (other modes get complainy about "this").

Below is the whole file. You can run it on Chicken with just "csi object.scm"

https://gist.github.com/johnnyb/211e105882248e892fa485327039cc90

I also tried to use let-syntax and use (this) as a syntax specifier to refer to the (this) variable. But, as far as I could tell, it wasn't letting me directly access a variable of my own making within the syntax rewriting.

BONUS QUESTION: What is an easy way to see the result of a syntax-rules transformation for debugging? Is there some way to get chicken (or something else) to do the transformation and spit out the result? I tried some stuff on DrRacket, but it doesn't work in R5RS mode.


Solution

  • I recognize that this is slightly unhygienic, but isn't that at least part of the point of specifying literals?

    No, the literals exist so you can match literally on keywords, like for example the => or the else in a cond clause. It's still hygienic because if => or else is lexically bound to some value, that has precedence:

    (let ((else #f))
      (cond (else (display "hi!\n")))) ;; Will not print
    

    Now, you could write a very tedious macro that matches this at any possible place and nesting level in the expansion, but that will never be complete, and it would not nest lexically, either.

    It is possible to do what you're trying to do using what has become known as Petrofsky extraction, but it's a total and utter hack and abuse of syntax-rules and it does not work (consistently) in the presence of modules across implementations (for example, exactly in CHICKEN we've had a complaint that we accidentally "broke" this feature).

    What I'd suggest is writing a syntax-rules macro that accepts an identifier in its input which will be bound to the current object, then write one trivial unhygienic macro that calls that other macro with the hardcoded identifier this as input.

    What is an easy way to see the result of a syntax-rules transformation for debugging? Is there some way to get chicken (or something else) to do the transformation and spit out the result? I tried some stuff on DrRacket, but it doesn't work in R5RS mode.

    In csi, you can use ,x (macro-call), but it will only do one level of expansion.

    A common trick that works in every Scheme implementation is to change your macro definition to quote its output. So, it expands not to (foo) but to '(foo). That way, you can just call the macro in the REPL and see its result immediately.