Hello,
Let's define a simple point type:
(define-record-type pt
(fields (mutable x)
(mutable y)))
In C++, we'd be able to define a method on the class and reference the
instance variables without qualification. Let's do that in Scheme:
(define (pt::norm p)
(import-pt p)
(sqrt (+ (sq x)
(sq y))))
You can see I'm referencing 'x' and 'y' without special accessors or
qualifiers. This magic is due to the 'import-pt' macro. We'll see that
later.
Also in C++, you'd be able to declare a variable to be of type pt and
have convenient access to it's components and methods. Let's do that
too:
(define (pt/n p n)
(is-pt p)
(make-pt (/ p.x n)
(/ p.y n)))
Let's try it out:
> (pt/n (make-pt 3 4) 5)
#[pt 3/5 4/5]
In the definition of 'pt/n' I'm accessing the 'x' component via 'p.x'
and the 'y' component via 'p.y'. The magic in this case is due to
'is-pt'; we'll see it later. By the way, the 'x' component can be set
like so:
(p.x! 4)
In a method on 'pt' setting 'x' would simply be:
(x! 4)
Earlier I said that 'pt::norm' is a method on the 'pt' record type. In
C++ we'd be able to reference that method with no qualification from the
definition of another method on 'pt'. Let's do that:
(define (pt::normalize p)
(import-pt p)
(pt/n p (norm)))
So 'normalize' is a method on 'pt' and the body is referencing the
'norm' method without qualification.
Here's the 'import-pt' macro:
(define-syntax import-pt
(lambda (stx)
(syntax-case stx ()
((import-pt p)
(with-syntax ( (x (gen-id #'p "x"))
(y (gen-id #'p "y"))
(x! (gen-id #'p "x!"))
(y! (gen-id #'p "y!"))
(neg (gen-id #'p "neg"))
(norm (gen-id #'p "norm"))
(normalize (gen-id #'p "normalize")) )
#'(begin
(define-syntax x
(identifier-syntax
(pt-x p)))
(define-syntax y
(identifier-syntax
(pt-y p)))
(define-syntax x!
(syntax-rules ()
((x! val)
(pt-x-set! p val))))
(define-syntax y!
(syntax-rules ()
((y! val)
(pt-y-set! p val))))
(define-syntax neg
(syntax-rules ()
((neg)
(pt::neg p))))
(define-syntax norm
(syntax-rules ()
((norm)
(pt::norm p))))
(define-syntax normalize
(syntax-rules ()
((normalize)
(pt::normalize p))))))))))
And the 'is-pt' macro:
(define-syntax is-pt
(lambda (stx)
(syntax-case stx ()
((is-pt p)
(with-syntax ( (p.x (gen-id #'p #'p ".x"))
(p.y (gen-id #'p #'p ".y"))
(p.x! (gen-id #'p #'p ".x!"))
(p.y! (gen-id #'p #'p ".y!"))
(p.neg (gen-id #'p #'p ".neg"))
(p.norm (gen-id #'p #'p ".norm"))
(p.normalize (gen-id #'p #'p ".normalize")) )
#'(begin
(define-syntax p.x
(identifier-syntax
(pt-x p)))
(define-syntax p.y
(identifier-syntax
(pt-y p)))
(define-syntax p.x!
(syntax-rules ()
((p.x! val)
(pt-x-set! p val))))
(define-syntax p.y!
(syntax-rules ()
((p.y! val)
(pt-y-set! p val))))
(define-syntax p.neg
(syntax-rules ()
((p.neg)
(pt::neg p))))
(define-syntax p.norm
(syntax-rules ()
((p.norm)
(pt::norm p))))
(define-syntax p.normalize
(syntax-rules ()
((p.normalize)
(pt::normalize p))))))))))
A library '(pt-class)' containing the examples above is at:
http://gist.github.com/243752
Of course, having to manually define macros like 'import-pt' and 'is-pt'
for each record type you'd like to use in this manner is not so
convenient. Having them auto-generated from a record definition and it's
corresponding methods would be very cool.
Ed