Scheming

Rather than junk up the Bakedown Thread with scheme stuff I’m starting a thread here where I can collect thoughts.

I’ve been a little miffed about record-types. Scheme is a minimalist Lisp dialect. People often talk about the simplicity of Lisp but Common Lisp and Clojure, the two big players, are enormous language and quite recondite. Scheme, in contrast, is almost literally an experiment to see how much you can get out of a very small language. Since Scheme has anonymous functions you can do pretty much anything you want - functions can famously represent any sort of data structure whatever.

Consider:

(define (make-person first-name last-name)
 (lambda (message . other-arguments)
   (case message
     ((first-name) first-name)
     ((last-name) last-name)
     ((set-first-name!)
      (set! first-name (car other-arguments)))
     ((set-last-name!)
      (set! last-name (car other-arguments)))
     ((whole-name)
      (string-append first-name " " last-name)))))

(define p1 (make-person "Henry" "Longfellow"))

(p1 'first-name) ; -> "Henry"
(p1 'set-last-name! "Higgens")
(p1 'whole-name) ; ->  "Henry Higgens"

In the above example an anonymous function serves the same purpose as a dictionary or object type. A little bit more ingenuity suffices to model single or multiple inheritance and other flourishes this way. For instance, we could pass a prototype object (also an anonymous function) into our constructor and if the case statement falls through we can pass the message up the inheritance chain.

There is much to be said for the powers of such an approach but one big issue with it is that every object in our object system has the following behavior:

(procedure? o) ; -> #t (true)

Procedures in Scheme are totally opaque. We don’t know even how many arguments they take. And now our object types are non-disjoint with the entire universe of Scheme procedures, many of which cannot even be made in principle to support useful messages like:

(o 'get-class)
(o 'instance-of? <some-class>)

This opacity has benefits - it forces us to rely on our objects to dispatch on type, for instance. But it also makes debugging hard. What happens when someone accidentally passes a normal procedure into a function expecting a procedure qua object? Depending on the situation, we may not even find out about the problem near the call site, which is a recipe for the worst sort of bug.

Thus, record-types. Modern Schemes define various incompatible ways of defining record types which are collections of named slots along with a type which is disjoint from any other scheme type.

(define-record-type point (fields x y))
(point? (make-point 10 11)) ; -> #t
(point-x (make-point 12 13)) ; -> 12

No other type predicates will be #t for instances of the point record.

The rub here is the difference between R6RS records and R7RS records. R6RS records support record type inheritance - you can specify a parent record type in R6RS with all the expected behavior. Child record types have all the slots of the parent type, are #t under the parent type type predicate and also #t under their type predicate and have their slots.

R7RS records do not have this functionality. Unfortunately, some of my code depends on it. I’m presently wrestling with whether its worth porting or writing an implementation of R6RS records on top of my current Scheme (kawa) or whether I should do a partial implementation or just rewrite my code by hand to use the Java class system.

This is real Scheme shit, basically.

2 Likes

One of the big things I like about Scheme is that even when there isn’t pattern matching in the host language I can implement it. I carry around a pattern matcher that I wrote originally in Emacs Lisp that I port to every Lisp/Scheme I use regularly.

I’ve got it working in Kawa now but sometimes I get these ominous warnings:

jmonkey3d-lacraw/src/lib/mecs-protocols.scm:12:7: warning - 
 initialization of r never finishes

This is an ominous sort of error which seems to have to do with this syntax-transformer:

    (define-syntax match-helper
      (lambda (expr)
	(syntax-case expr ()
	  ((_ original-expression value)
	   #'(error "match" (format "Match failure in form ~a" 'original-expression) value))
	  ((_ original-expression value (pattern body0 body ...) term ...)
	   #'(let ((r (match1-or-fail value pattern body0 body ...)))
	       (if (not (eq? r *shadchen-fail*))
		   r
		   (match-helper original-expression value term ...)))))))

I’m not seeing such an error in the Chez-Scheme version of this library. It might have to do with the fact that macro-expansion happens at slightly different times and in different ways in Kawa. All my shadchen tests pass, so I’m not sure whether it matters.