; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING. ; Generic procedure package ; This is written in fairly portable Scheme. It needs: ; Scheme 48 low-level macros (explicit renaming), in one small place. ; (CALL-ERROR message proc arg ...) - signal an error. ; Record package and DEFINE-RECORD-TYPES macro. ; An object :RECORD-TYPE which is the record type descriptor for ; record type descriptors (records are assumed to be records). ; This wouldn't be difficult to change. ; A RECORD? predicate (not essential - only for defining a DISCLOSE ; method for records). ; -------------------- ; Simple types. ; More specific types have higher priorities. The priorities are used ; to establish the ordinary in which type predicates are called. (define-record-type simple-type :simple-type (really-make-simple-type supers predicate priority id) simple-type? (supers simple-type-superiors) (predicate simple-type-predicate) (priority simple-type-priority) (id simple-type-id) (more)) ;if needed later (define-record-discloser :simple-type (lambda (c) `(simple-type ,(simple-type-id c)))) (define (make-simple-type supers predicate id) (make-immutable! (really-make-simple-type supers predicate (compute-priority supers) id))) (define (compute-priority supers) (if (null? supers) 0 (+ (apply max (map %type-priority supers)) *increment*))) (define *increment* 10) ; These two procedures will become generic later, but must exist early ; in order to be able to bootstrap the method definition mechanism. (define (%type-priority type) (cond ((simple-type? type) (simple-type-priority type)) ((record-type? type) (record-type-priority type)) (else (type-priority type)))) ;generic (define (%type-predicate type) (cond ((simple-type? type) (simple-type-predicate type)) ((record-type? type) (record-predicate type)) (else (type-predicate type)))) ;generic (define (%same-type? t1 t2) (or (eq? t1 t2) (if (simple-type? t1) #f (if (record-type? t1) #f (same-type? t1 t2))))) (define-syntax define-simple-type (syntax-rules () ((define-simple-type ?name (?super ...) ?pred) (define ?name (make-simple-type (list ?super ...) ?pred '?name))))) ; -------------------- ; Built-in Scheme types (define-simple-type :syntax () #f) (define-simple-type :values () #f) ;any number of values (define (value? x) #t) (define-simple-type :value (:values) value?) (define-simple-type :zero (:values) (lambda (x) #f)) (define-simple-type :number (:value) number?) (define-simple-type :complex (:number) complex?) (define-simple-type :real (:complex) real?) (define-simple-type :rational (:real) rational?) (define-simple-type :integer (:rational) integer?) (define-simple-type :exact-integer (:integer) (lambda (n) (and (integer? n) (exact? n)))) (define-simple-type :boolean (:value) boolean?) (define-simple-type :symbol (:value) symbol?) (define-simple-type :char (:value) char?) (define-simple-type :null (:value) null?) (define-simple-type :pair (:value) pair?) (define-simple-type :vector (:value) vector?) (define-simple-type :string (:value) string?) (define-simple-type :procedure (:value) procedure?) (define-simple-type :input-port (:value) input-port?) (define-simple-type :output-port (:value) output-port?) (define-simple-type :eof-object (:value) eof-object?) ; If there is no RECORD? predicate, do ; (define-simple-type :record (:value) value?) ; and change the DISCLOSE method for records to ; (or (disclose-record obj) (next-method)). (define-simple-type :record (:value) record?) ; Given a record type, RECORD-TYPE-PRIORITY returns its priority. ; Here we establish that every record type is a direct subtype of the ; :RECORD type. (define record-type-priority (let ((r-priority (simple-type-priority (make-simple-type (list :record) #f #f)))) (lambda (rt) r-priority))) ; -------------------- ; Method-info records are triples . (define-record-type method-info :method-info (really-make-method-info types n-ary? proc) method-info? (types method-info-types) (n-ary? method-info-n-ary?) (proc method-info-proc)) (define (make-method-info types n-ary? proc) (make-immutable! (really-make-method-info types n-ary? proc))) (define-record-discloser :method-info (lambda (info) `(method-info ,(method-info-types info) ,(method-info-n-ary? info)))) ; -------------------- ; Method lists ; A method list is a list of method-info records, sorted in order from ; most specific to least specific. (define (empty-method-list) '()) ; insert-method inserts an entry into a method list so that the most ; specific methods come earliest in the list. The last method should ; be a default method or error signal(l)er. (define (insert-method info ms) (let recur ((ms ms)) (if (null? ms) (cons info ms) (if (more-specific? (car ms) info) (cons (car ms) (recur (cdr ms))) (cons info (if (same-applicability? (car ms) info) (cdr ms) ms)))))) ; Replace an existing method with identical domain. (define (same-applicability? info1 info2) (and (every2 %same-type? (method-info-types info1) (method-info-types info2)) (eq? (method-info-n-ary? info1) (method-info-n-ary? info2)))) (define (every2 pred l1 l2) (if (null? l1) (null? l2) (if (null? l2) #f (and (pred (car l1) (car l2)) (every2 pred (cdr l1) (cdr l2)))))) ; This interacts with methods->perform, below. ; In this version, it's supposed to be a total order. (define (more-specific? info1 info2) (let ((t1 (method-info-types info1)) (t2 (method-info-types info2))) (let ((l1 (length t1)) (l2 (length t2)) (foo? (and (not (method-info-n-ary? info1)) (method-info-n-ary? info2)))) (if (= l1 l2) (or foo? (let loop ((l1 t1) (l2 t2)) (if (null? l2) #f (or (more-specific-type? (car l1) (car l2)) (and (%same-type? (car l1) (car l2)) (loop (cdr l1) (cdr l2))))))) (and (> l1 l2) foo?))))) (define (more-specific-type? t1 t2) (> (%type-priority t1) (%type-priority t2))) ; -------------------- ; A method table is a cell that contains a method list. ; Note that the method table is not reachable from the generic ; procedure. This means good things for the GC. (define-record-type method-table :method-table (really-make-method-table methods prototype generic get-perform set-perform! id) method-table? (methods method-table-methods set-method-table-methods!) (prototype method-table-prototype) (generic make-generic) (get-perform method-table-get-perform) (set-perform! method-table-set-perform!) (id method-table-id)) (define-record-discloser :method-table (lambda (t) `(method-table ,(method-table-id t)))) (define (make-method-table id . option) (let* ((prototype (if (null? option) (make-method-info '() #t #f) (car option))) (mtable (call-with-values make-cell-for-generic (lambda (generic get-perform set-perform!) (really-make-method-table '() prototype generic get-perform set-perform! id))))) (set-final-method! mtable (lambda (next-method . args) (apply call-error "invalid or unimplemented operation" id args))) mtable)) (define (make-cell-for-generic) (let ((perform #f)) ;; PERFORM always caches (METHODS->PERFORM method-list prototype). (values (lambda args (perform args)) ;Generic proc (lambda () perform) (lambda (new) (set! perform new))))) (define (add-to-method-table! mtable info) (let ((l (insert-method info (method-table-methods mtable)))) (set-method-table-methods! mtable l) ((method-table-set-perform! mtable) (methods->perform l (method-table-prototype mtable))))) (define (set-final-method! mtable proc) (add-to-method-table! mtable (make-method-info '() #t proc))) (define (apply-generic mtable args) ;; (apply (make-generic mtable) args) (((method-table-get-perform mtable)) args)) ;+++ ; DEFINE-GENERIC (define-syntax define-generic (syntax-rules () ((define-generic ?name ?mtable-name) (begin (define ?mtable-name (make-method-table '?name)) (define ?name (make-generic ?mtable-name)))) ((define-generic ?name ?mtable-name (?spec . ?specs)) (begin (define ?mtable-name (make-method-table '?name (method-info ?name ("next" next-method ?spec . ?specs) (next-method)))) (define ?name (make-generic ?mtable-name)))))) ; -------------------- ; Method combination. ; Here is the specification: ;(define (apply-generic mtable args) ; (let loop ((ms (method-table-methods mtable))) ; (let ((next-method (lambda () (loop (cdr ms))))) ; (if (let test ((ts (method-info-types (car ms))) ; (args args)) ; (if (null? ts) ; (or (null? args) ; (method-info-n-ary? (car ms))) ; (and ((%type-predicate (car ts)) (car args)) ; (test (cdr ts) (cdr args))))) ; (apply (method-info-proc (car ms)) ; next-method ; args) ; (next-method))))) ; (perform arg-list) ; (apply proc next-method-thunk arg-list) ; This version of METHODS->PERFORM simply marches through all the ; methods, looking for one that handles the operation. ; The prototype is currently ignored, but it could be put to good use. (define (methods->perform l prototype) (let recur ((l l)) (let* ((info (car l)) (proc (method-info-proc info))) (if (null? (cdr l)) (last-action proc) (one-action (argument-sequence-predicate info) proc (recur (cdr l))))))) (define (last-action proc) (lambda (args) (apply proc #f args))) (define (one-action pred proc perform-next) (lambda (args) (if (pred args) (apply proc (lambda () (perform-next args)) ; next-method args) (perform-next args)))) (define (argument-sequence-predicate info) (let recur ((types (method-info-types info))) (if (null? types) (if (method-info-n-ary? info) value? null?) (let ((pred (%type-predicate (car types))) (check-rest (recur (cdr types)))) (if (eq? pred value?) (check-for-next check-rest) ;+++ (check-next pred check-rest)))))) (define (check-for-next check-rest) (lambda (args) (if (null? args) #f (check-rest (cdr args))))) (define (check-next pred check-rest) (lambda (args) (if (null? args) #f (if (pred (car args)) (check-rest (cdr args)) #f)))) ; -------------------- ; METHOD-INFO macro. ; Returns a method-info record. ; You can specify the name of the next-method parameter by saying ; (method-info my-name (x y "next" n) body ...) ; Otherwise, the next-method parameter will be named next-method. ; Just pretend it's Dylan and that #next reads as "next". (define-syntax method-info (syntax-rules () ((method-info ?id ?formals ?body ...) (method-internal ?formals () () #f ?id ?body ...)))) (define-syntax method-internal (syntax-rules () ((method-internal ((?formal1 ?type1) . ?specs) (?formal ...) (?type ...) ?next . ?rest) (method-internal ?specs (?formal ... ?formal1) (?type ... ?type1) ?next . ?rest)) ((method-internal ("next" ?next . ?specs) (?formal ...) (?type ...) ?ignore . ?rest) (method-internal ?specs (?formal ...) (?type ...) ?next . ?rest)) ((method-internal (?spec . ?specs) (?formal ...) (?type ...) ?next . ?rest) (method-internal ?specs (?formal ... ?spec) (?type ... :value) ?next . ?rest)) ((method-internal ?rest (?formal ...) (?type ...) ?next ?id ?body ...) (make-method-info (list ?type ...) (not (null? '?rest)) (let ((?id (with-next-method ?next (?formal ... . ?rest) ?body ...))) ;; The (let ...) is a hack for the Scheme 48 ;; byte code compiler, which will remember ;; ?id as the procedure's name. This should ;; aid debugging a little bit since the name ;; shows up in backtraces and the inspector. ?id))))) ; Non-hygienic, a la Dylan (define-syntax with-next-method (cons (lambda (e r c) (let ((next (or (cadr e) 'next-method))) `(,(r 'lambda) (,next ,@(caddr e)) ,@(cdddr e)))) '(lambda))) ; DEFINE-METHOD macro. (define-syntax define-method (syntax-rules () ((define-method ?mtable ?formals ?body ...) (add-method! ?mtable (method-info ?mtable ?formals ?body ...))))) (define-generic add-method! &add-method! (mtable info)) (let ((info (method-info add-method! ((mtable :method-table) (info :method-info)) (add-to-method-table! mtable info)))) (add-to-method-table! &add-method! info)) ; -------------------- ; Generic functions on types: sort of a meta-object protocol, huh? (define-generic type-predicate &type-predicate (t)) (define-method &type-predicate ((t :record-type)) (record-predicate t)) (define-method &type-predicate ((t :simple-type)) (simple-type-predicate t)) (define-generic type-priority &type-priority (t)) (define-method &type-priority ((t :record-type)) (record-type-priority t)) (define-method &type-priority ((t :simple-type)) (simple-type-priority t)) (define-generic type-superiors &type-superiors (t)) (define-method &type-superiors ((t :record-type)) (list :record)) (define-method &type-superiors ((t :simple-type)) (simple-type-superiors t)) ; Type equivalence (define-generic same-type? &same-type? (t1 t2)) (define-method &same-type? (t1 t2) (eq? t1 t2)) (define-method &same-type? ((t1 :simple-type) (t2 :simple-type)) (and (eq? (simple-type-predicate t1) (simple-type-predicate t2)) (eq? (simple-type-id t1) (simple-type-id t2)))) ;? ; -------------------- ; Singleton types. (define-record-type singleton :singleton (singleton value) (value singleton-value)) (define-record-discloser :singleton (lambda (s) `(singleton ,(singleton-value s)))) (define (compare-to val) (lambda (x) (eqv? x val))) (define-method &type-predicate ((s :singleton)) (compare-to (singleton-value s))) (define-method &type-priority ((s :singleton)) 1000000) (define-method &same-type? ((s1 :singleton) (s2 :singleton)) (eqv? (singleton-value s1) (singleton-value s2))) ; -------------------- ; DISCLOSE ; A generic procedure for producing printed representations. ; Should return one of ; - A list (symbol info ...), to be printed as #{Symbol info ...} ; - #f, meaning no information available on how to print. ; This is intended to be used not only by write and display, but also by ; the pretty printer. (define-generic disclose &disclose (x)) (define-method &disclose (obj) #f) (define-method &disclose ((obj :record)) (or (disclose-record obj) '(record))) (define-method &add-method! ((d (singleton &disclose)) info) (let ((t (car (method-info-types info)))) (if (record-type? t) (define-record-discloser t (proc->discloser (method-info-proc info))) (next-method)))) (define (proc->discloser proc) (lambda (arg) (proc (lambda () #f) arg))) ;(define-method &disclose ((s :singleton)) ; `(singleton ,(singleton-value s)))