4e987026 |
;;; 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))))))
|