;;; Basic DI structure: ;;; a. Create the set of instances ;;; b. Expand the context of each potential instance. ;;; c. Once b. reaches a fixpoint, fill in the ast for the generated instances (define *di-context-changed* '#f) (define (add-derived-instances modules) (let ((insts '())) (walk-modules modules (lambda () (setf insts (append (find-derivable-instances) insts)))) (compute-di-fixpoint insts) (dolist (inst insts) (when (instance-ok? inst) (create-instance-fns inst) (push inst (module-instance-defs (table-entry *modules* (def-module (instance-algdata inst))))))))) (define (compute-di-fixpoint insts) (setf *di-context-changed* '#f) (dolist (inst insts) (propagate-di-context inst)) (when *di-context-changed* (compute-di-fixpoint insts))) ;;; Create instance decls for all derived instances in a module. Filter ;;; out underivable instances (Ix & Enum only) (define (find-derivable-instances) (let ((algs (module-alg-defs *module*)) (insts '())) (dolist (alg algs) (dolist (class (algdata-deriving alg)) (cond ((memq class (list (core-symbol "Eq") (core-symbol "Ord") (core-symbol "Text") (core-symbol "Binary"))) (setf insts (add-derivable-instance insts alg class '#f))) ((eq? class *printer-class*) (setf insts (add-derivable-instance insts alg (core-symbol "Text") '#t))) ((eq? class (core-symbol "Ix")) (if (or (algdata-enum? alg) (algdata-tuple? alg)) (setf insts (add-derivable-instance insts alg class '#f)) (signal-cant-derive-ix alg))) ((eq? class (core-symbol "Enum")) (if (algdata-enum? alg) (setf insts (add-derivable-instance insts alg class '#f)) (signal-cant-derive-enum alg))) (else (signal-not-derivable class))))) insts)) (define (signal-cant-derive-ix alg) (phase-error 'cant-derive-IX "An Ix instance for ~A cannot be derived. It is not an enumeration~%~ or single-constructor datatype." alg)) (define (signal-cant-derive-enum alg) (phase-error 'cant-derive-Enum "An Enum instance for ~A cannot be derived. It is not an enumeration." alg)) (define (signal-not-derivable class) (recoverable-error 'not-derivable "Class ~A is not one of the classes that permits derived instances." class)) ;; This adds a provisional instance template. Of course, there may already ;;; be an instance (error!) (define (add-derivable-instance insts alg cls sp) (let ((existing-inst (lookup-instance alg cls))) (cond ((eq? existing-inst '#f) (let ((inst (new-instance cls alg (algdata-tyvars alg)))) (setf (instance-context inst) (algdata-context alg)) (setf (instance-decls inst) '()) (setf (instance-ok? inst) '#t) (setf (instance-suppress-readers? inst) sp) (cons inst insts))) (else (signal-instance-exists alg cls) insts)))) (define (signal-instance-exists alg cls) (recoverable-error 'instance-exists "An instance for type ~A in class ~A already exists;~%~ the deriving clause is being ignored." alg cls)) ;;; This updates all instance contexts for an algdata. Each derivable ;;; instance generates a recursive context for every field. If a ;;; component cannot satisfy the desired context, the ok? field is set to ;;; #f to mark the instance as bogus. (define (propagate-di-context inst) (when (instance-ok? inst) (propagate-constructor-contexts inst (algdata-constrs (instance-algdata inst))))) ;;; These two functions propagate the context to ever field of every ;;; constructor (define (propagate-constructor-contexts inst constrs) (or (null? constrs) (and (propagate-contexts inst (instance-class inst) (con-types (car constrs))) (propagate-constructor-contexts inst (cdr constrs))))) (define (propagate-contexts inst class types) (or (null? types) (and (propagate-type-context inst class (car types)) (propagate-contexts inst class (cdr types))))) ;;; This propagates a context out to a given type. The type can only contain ;;; the tyvars which are args to the algdata. (define (propagate-type-context inst class type) (cond ((tyvar? type) (cond ((single-ast-context-implies? (instance-context inst) class (tyvar-name type)) '#t) (else (setf *di-context-changed* '#t) (setf (instance-context inst) (augment-context (instance-context inst) class (tyvar-name type))) '#t))) ((synonym? (tycon-def type)) (propagate-type-context inst class (expand-synonym type))) (else (let* ((algdata (tycon-def type)) ; must be a algdata (args (tycon-args type)) (new-inst (lookup-instance algdata class))) (cond ((or (eq? new-inst '#f) (not (instance-ok? new-inst))) (signal-cannot-derive-instance (instance-class inst) (instance-algdata inst)) (setf (instance-ok? inst) '#f) (setf *di-context-changed* '#t) '#f) (else (propagate-instance-contexts inst (instance-context new-inst) (instance-tyvars new-inst) args))))))) (define (single-ast-context-implies? ast-context class tyvar) (cond ((null? ast-context) '#f) ((eq? tyvar (context-tyvar (car ast-context))) (let ((class1 (class-ref-class (context-class (car ast-context))))) (or (eq? class1 class) (memq class (class-super* class1)) (single-ast-context-implies? (cdr ast-context) class tyvar)))) (else (single-ast-context-implies? (cdr ast-context) class tyvar)))) ;;; *** This message makes no sense to me. What is the problem that ;;; *** makes it impossible to derive the instance? (define (signal-cannot-derive-instance class alg) (phase-error 'cannot-derive-instance "Instance ~A(~A) cannot be derived." class alg)) ;;; This propagates contexts into structure components. The context ;;; changes due to the context associated with the various instance ;;; decls encountered. ;;; Here's the plan for expanding Cls(Alg t1 t2 .. tn) using ;;; instance (Cls1(vx),Cls2(vy),...) => Cls(Alg(v1 v2 .. vn)) ;;; for each Clsx in the instance context, propagate Clsx to the ;;; ti corresponding to vx, where vx must be in the set vi. (define (propagate-instance-contexts inst contexts tyvars args) (or (null? contexts) (and (propagate-type-context inst (class-ref-class (context-class (car contexts))) (find-corresponding-tyvar (context-tyvar (car contexts)) tyvars args)) (propagate-instance-contexts inst (cdr contexts) tyvars args)))) ;;; Given the t(i) and the v(i), return the t corresponding to a v. (define (find-corresponding-tyvar tyvar tyvars args) (if (eq? tyvar (car tyvars)) (car args) (find-corresponding-tyvar tyvar (cdr tyvars) (cdr args)))) ;;; 1 level type synonym expansion (define (expand-synonym type) (let* ((synonym (tycon-def type)) (args (synonym-args synonym)) (body (synonym-body synonym))) (let ((alist (map (lambda (tyvar arg) (tuple tyvar arg)) args (tycon-args type)))) (copy-synonym-body body alist)))) (define (copy-synonym-body type alist) (if (tyvar? type) (tuple-2-2 (assq (tyvar-name type) alist)) (make tycon (def (tycon-def type)) (name (tycon-name type)) (args (map (lambda (ty) (copy-synonym-body ty alist)) (tycon-args type)))))) ;;; This fills in the body decls for an instance function. (define (create-instance-fns inst) (let ((class (instance-class inst)) (alg (instance-algdata inst))) (cond ((eq? class (core-symbol "Eq")) (add-instance inst (eq-fns alg))) ((eq? class (core-symbol "Ord")) (add-instance inst (ord-fns alg))) ((eq? class (core-symbol "Ix")) (add-instance inst (ix-fns alg))) ((eq? class (core-symbol "Enum")) (add-instance inst (enum-fns alg))) ((eq? class (core-symbol "Text")) (add-instance inst (text-fns alg (instance-suppress-readers? inst)))) ((eq? class (core-symbol "Binary")) (add-instance inst (binary-fns alg)))))) (define (add-instance inst decls) (setf (instance-decls inst) decls)) ;;; Add class(var) to a context, removing any contexts made redundant by ;;; the new addition. Example: adding Ord a to (Eq a, Eq b) would yield ;;; (Ord a,Eq b). (define (augment-context contexts cl var) (cons (**context (**class/def cl) var) (remove-implied-contexts cl var contexts))) (define (remove-implied-contexts class1 tyvar1 contexts) (if (null? contexts) '#f (with-slots context (class tyvar) (car contexts) (let ((rest (remove-implied-contexts class1 tyvar1 (cdr contexts))) (class2 (class-ref-class class))) (if (and (eq? tyvar1 tyvar) (memq class2 (class-super* class1))) rest (cons (car contexts) rest))))))