0

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.

johnnyb
  • 622
  • 5
  • 17

1 Answers1

1

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.

sjamaan
  • 2,282
  • 10
  • 19
  • Thanks for the reply! I found some interesting things.... 1) Although my code *didn't* work as published, if I did your "quote" trick to see the output, and *then* copy/pasted the results, it *did* work! I don't know if this is a standard part of Scheme or something weird with Chicken scheme. 2) I used this to my advantage. I did a "let" to assign the results of the quote trick to a variable, and then did an "eval" on the result. Viola, It worked! – johnnyb Apr 23 '18 at 20:50
  • Here is a working implementation: https://gist.github.com/johnnyb/0e295c4bc64b7e37878fd6b3a4e21cdd – johnnyb Apr 23 '18 at 20:54
  • This `eval` trick works because all syntax information has been stripped. In your original code you had `(let ((this #f)) ...)`; that will be converted to something like `(let ((this123 #f)) ...)`, and any other occurrences of `this` in the macro definition will be translated to `this123`, whereas user-supplied `this` will be translated to (say) `this456`. But when you eval the quoted output, all those `this` identifiers will have been "flattened" (stripped of syntax information) and therefore collapse to the same. In general that's not a good idea :) – sjamaan Apr 24 '18 at 04:59