Skip to content

Commit cb3cec6

Browse files
authored
Merge pull request #71 from iitalics/refactor
Refactor typecheck.rkt
2 parents 381b3c4 + 22b4332 commit cb3cec6

File tree

4 files changed

+301
-281
lines changed

4 files changed

+301
-281
lines changed

hackett-lib/hackett/private/base.rkt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
(for-syntax hackett/private/infix
1818
hackett/private/typecheck
19+
hackett/private/typeclass
1920
hackett/private/util/list
2021
hackett/private/util/stx))
2122

2223
(provide (for-syntax (all-from-out hackett/private/typecheck)
24+
(all-from-out hackett/private/typeclass)
2325
τs⇔/λ! τ⇔/λ! τ⇔! τ⇐/λ! τ⇐! τ⇒/λ! τ⇒! τ⇒app! τs⇒!)
2426
(rename-out [#%top @%top])
2527
@%module-begin @%datum @%app
@@ -239,9 +241,7 @@
239241
#:literal-sets [type-literals]
240242
[(#%type:wobbly-var x^)
241243
#:with [x1^ x2^] (generate-temporaries #'[x^ x^])
242-
(modify-type-context
243-
#{snoc % (ctx:solution #'x^ (template
244-
(?->* (#%type:wobbly-var x1^) (#%type:wobbly-var x2^))))})
244+
(type-inst-l! #'x^ (template (?->* (#%type:wobbly-var x1^) (#%type:wobbly-var x2^))))
245245
(values (quasisyntax/loc src
246246
(lazy- (#%app- (force- #,e_fn) #,(τ⇐! e_arg #'(#%type:wobbly-var x1^)))))
247247
#'(#%type:wobbly-var x2^))]

hackett-lib/hackett/private/class.rkt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
syntax/parse/define
1515

1616
(for-syntax hackett/private/infix
17+
hackett/private/typeclass
1718
hackett/private/util/stx)
1819
(except-in hackett/private/base @%app)
1920
(only-in (unmangle-types-in #:no-introduce (only-types-in hackett/private/kernel))

hackett-lib/hackett/private/typecheck.rkt

Lines changed: 13 additions & 278 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,26 @@
11
#lang curly-fn racket/base
22

3-
; This module contains the core implementation of the Hackett typechecker, as well as the core type
4-
; representations. The Hackett typechecker operates using a mutable typechecker context implemented
5-
; as a Racket parameter, which contains information about solutions for existing solver variables.
3+
; This module contains the core implementation of the Hackett typechecker. The Hackett typechecker
4+
; operates using a mutable typechecker context implemented as a Racket parameter, which contains
5+
; information about solutions for existing solver variables.
66
;
7-
; The core typechecker implements typing subsumption rules and the constraint solver for resolving
8-
; typeclass instances. Other functionality is deferred to the implementation site of Hackett forms.
9-
; The functions that perform the actual process of type inference (via recursive expansion) are
10-
; defined in hackett/private/base, and they provide the primary interface to the various typechecker
11-
; functions in this module.
7+
; The core typechecker implements typing subsumption rules and variable instantiation. Other
8+
; functionality is deferred to the implementation site of Hackett forms. The functions that perform
9+
; the actual process of type inference (via recursive expansion) are defined in hackett/private/base,
10+
; and they provide the primary interface to the various typechecker functions in this module.
1211

1312
(require (for-template racket/base
14-
syntax/id-table
1513
hackett/private/type-language)
1614
(for-syntax racket/base
1715
syntax/transformer)
18-
data/gvector
16+
1917
racket/contract
2018
racket/format
2119
racket/function
2220
racket/list
2321
racket/match
2422
racket/string
2523
racket/syntax
26-
racket/stxparam-exptime
27-
syntax/id-table
2824
syntax/parse
2925
syntax/parse/class/local-value
3026
syntax/parse/define
@@ -35,29 +31,13 @@
3531
hackett/private/util/list
3632
hackett/private/util/stx)
3733

38-
(provide (contract-out [struct ctx:solution ([x^ identifier?] [t type?])]
39-
[struct class:info ([vars (listof identifier?)]
40-
[method-table immutable-free-id-table?]
41-
[default-methods immutable-free-id-table?]
42-
[superclasses (listof constr?)]
43-
[deriving-transformer (or/c (-> syntax? syntax?) #f)])]
44-
[struct class:instance ([class class:info?]
45-
[vars (listof identifier?)]
46-
[subgoals (listof constr?)]
47-
[ts (listof (and/c type? type-mono?))]
48-
[dict-expr syntax?])])
49-
type? type=? constr? type-mono? type-vars^ type->string
34+
(provide type? type=? constr? type-mono? type-vars^ type->string
5035
generalize inst insts type<:/full! type<:/elaborate! type<:! type-inst-l! type-inst-r!
51-
ctx-elem? ctx? ctx-elem=? ctx-member? ctx-remove
52-
ctx-find-solution current-ctx-solution apply-subst apply-current-subst
36+
apply-subst apply-current-subst
5337
current-type-context modify-type-context
54-
register-global-class-instance! constr->instances lookup-instance!
55-
reduce-context type-reduce-context
5638
attach-type attach-expected get-type get-expected apply-current-subst-in-tooltips
5739
make-typed-var-transformer
58-
59-
(for-template (all-from-out hackett/private/type-language)
60-
local-class-instances @%superclasses-key))
40+
(for-template (all-from-out hackett/private/type-language)))
6141

6242
;; ---------------------------------------------------------------------------------------------------
6343
;; type operations
@@ -173,11 +153,9 @@
173153

174154
(define/contract (ctx-find-solution ctx x^)
175155
(-> ctx? identifier? (or/c type? #f))
176-
(and~> (findf #{and (ctx:solution? %) (free-identifier=? x^ (ctx:solution-x^ %))} ctx)
156+
(and~> (findf #{and (ctx:solution? %)
157+
(free-identifier=? x^ (ctx:solution-x^ %))} ctx)
177158
ctx:solution-t))
178-
(define/contract (current-ctx-solution x^)
179-
(-> identifier? (or/c type? #f))
180-
(ctx-find-solution (current-type-context) x^))
181159

182160
(define/contract (apply-subst ctx t)
183161
(-> ctx? type? type?)
@@ -226,48 +204,6 @@
226204
(-> (-> ctx? ctx?) void?)
227205
(current-type-context (f (current-type-context))))
228206

229-
;; ---------------------------------------------------------------------------------------------------
230-
;; instance contexts
231-
232-
(struct class:info (vars method-table default-methods superclasses deriving-transformer) #:transparent
233-
#:property prop:procedure
234-
(λ (info stx)
235-
((make-variable-like-transformer
236-
#`(#%type:con #,(syntax-parse stx
237-
[id:id #'id]
238-
[(id:id . _) #'id])))
239-
stx)))
240-
(struct class:instance (class vars subgoals ts dict-expr) #:transparent)
241-
242-
(define-syntax-class (class-id #:require-deriving-transformer? [require-deriving-transformer? #f])
243-
#:description "class id"
244-
#:attributes [local-value]
245-
[pattern
246-
{~var || (local-value class:info? #:failure-message "identifier was not bound to a class")}
247-
#:fail-unless (or (not require-deriving-transformer?)
248-
(class:info-deriving-transformer (attribute local-value)))
249-
"class is not derivable"])
250-
251-
(define global-class-instances (make-gvector))
252-
(define/contract (register-global-class-instance! instance)
253-
(-> class:instance? void?)
254-
(gvector-add! global-class-instances instance))
255-
256-
(module local-instances-stxparam racket/base
257-
(require (for-syntax racket/base) racket/stxparam)
258-
(provide local-class-instances)
259-
(define-syntax-parameter local-class-instances '()))
260-
(require (for-template 'local-instances-stxparam))
261-
262-
(define/contract (current-class-instances)
263-
(-> (listof class:instance?))
264-
(append (syntax-parameter-value #'local-class-instances)
265-
(gvector->list global-class-instances)))
266-
267-
(define (current-instances-of-class class)
268-
(-> class:info? (listof class:instance?))
269-
(filter #{eq? class (class:instance-class %)} (current-class-instances)))
270-
271207
;; ---------------------------------------------------------------------------------------------------
272208
;; subsumption, instantiation, and elaboration
273209

@@ -394,207 +330,6 @@
394330
[_ (error 'type-inst-r! (format "internal error: failed to instantiate ~a to a supertype of ~a"
395331
(type->string #`(#%type:wobbly-var #,x^)) (type->string t)))]))
396332

397-
; Performs one-way unification to see if a type matches another one. Unlike general unification,
398-
; one-way matching is asymmetric: it only solves wobbly variables in the first type argument, never in
399-
; the second. If unifying the two types would require unification in the second type, matching fails.
400-
; Also, matching is more restricted than unification: it never instantiates quantifiers in other type,
401-
; nor does it permit qualified types. If a quantifier or qualified type is encountered, matching
402-
; fails.
403-
(define/contract (types-match?! a b)
404-
(-> type? type? boolean?)
405-
(syntax-parse (list (apply-current-subst a) (apply-current-subst b))
406-
#:context 'match-types!
407-
#:literal-sets [type-literals]
408-
[[(#%type:rigid-var x^) (#%type:rigid-var y^)]
409-
#:when (free-identifier=? #'x^ #'y^)
410-
#t]
411-
[[(#%type:wobbly-var x^) t]
412-
#:when (type-mono? #'t)
413-
(modify-type-context #{snoc % (ctx:solution #'x^ #'t)})
414-
#t]
415-
[[(#%type:con a) (#%type:con b)]
416-
#:when (free-identifier=? #'a #'b)
417-
#t]
418-
[[(#%type:app a b) (#%type:app c d)]
419-
(and (types-match?! #'a #'c) (types-match?! #'b #'d))]
420-
[[_ _]
421-
#f]))
422-
423-
;; ---------------------------------------------------------------------------------------------------
424-
425-
(module superclasses-key racket/base
426-
(require (for-syntax racket/base))
427-
(provide @%superclasses-key)
428-
(define-syntax (@%superclasses-key stx)
429-
(raise-syntax-error #f "cannot be used as an expression" stx)))
430-
(require (for-template 'superclasses-key))
431-
432-
(define/contract constr->class:info+ts
433-
(-> constr? (values class:info? (listof type?)))
434-
(syntax-parser
435-
#:context 'constr->class:info
436-
#:literal-sets [type-literals]
437-
[(~#%type:app* (#%type:con class-id:class-id) ts ...)
438-
(values (attribute class-id.local-value) (attribute ts))]))
439-
440-
; Given a constraint, calculate the instances it brings in scope, including instances that can be
441-
; derived via superclasses. For example, the constraint (Monad m) brings in three instances, one for
442-
; Monad and two for Functor and Applicative.
443-
(define/contract (constr->instances constr dict-expr)
444-
(-> constr? syntax? (listof class:instance?))
445-
(let-values ([(class-info ts) (constr->class:info+ts constr)])
446-
(let* ([ts* (map apply-current-subst ts)]
447-
[instance (class:instance class-info '() '() ts* dict-expr)]
448-
; instantiate the superclass constraints, so for (Monad Unit), we get (Applicative Unit)
449-
; instead of (Applicative m)
450-
[insts-dict (map cons (class:info-vars class-info) ts*)]
451-
[super-constrs (map #{insts % insts-dict} (class:info-superclasses class-info))]
452-
[superclass-dict-expr #`(free-id-table-ref #,dict-expr #'@%superclasses-key)]
453-
[super-instances (for/list ([(super-constr i) (in-indexed (in-list super-constrs))])
454-
(constr->instances
455-
super-constr
456-
#`(vector-ref #,superclass-dict-expr '#,i)))])
457-
(cons instance (append* super-instances)))))
458-
459-
; Attempts to unify a type with an instance head with a type for the purposes of picking a typeclass.
460-
; If the match succeeds, it returns a list of instantiated subgoals for the instance, otherwise it
461-
; returns #f.
462-
(define/contract (unify-instance-head ts vars subgoals head)
463-
(-> (listof type?) (listof identifier?) (listof constr?) (listof (and/c type? type-mono?))
464-
(or/c (listof constr?) #f))
465-
(let* ([vars^ (generate-temporaries vars)]
466-
[var-subst (map #{cons %1 #`(#%type:wobbly-var #,%2)} vars vars^)]
467-
[head-inst (map #{insts % var-subst} head)]
468-
[subgoals-inst (map #{insts % var-subst} subgoals)])
469-
(and (andmap types-match?! head-inst ts)
470-
subgoals-inst)))
471-
472-
(define/contract (lookup-instance!
473-
constr
474-
#:src src
475-
#:failure-thunk [failure-thunk
476-
(λ ()
477-
(raise-syntax-error
478-
'typechecker
479-
(~a "could not deduce "
480-
(type->string (apply-current-subst constr)))
481-
src))])
482-
(->* [constr? #:src syntax?]
483-
[#:failure-thunk (-> any)]
484-
any) ; (values class:instance? (listof constr?))
485-
(define-values [class ts] (constr->class:info+ts constr))
486-
(define ts* (map apply-current-subst ts))
487-
(define instance+subgoals
488-
(for/or ([instance (in-list (current-instances-of-class class))])
489-
(let ([old-type-context (current-type-context)])
490-
(let ([constrs (unify-instance-head ts*
491-
(class:instance-vars instance)
492-
(class:instance-subgoals instance)
493-
(class:instance-ts instance))])
494-
(if constrs (list instance constrs)
495-
(begin (current-type-context old-type-context) #f))))))
496-
(if instance+subgoals
497-
(apply values instance+subgoals)
498-
(failure-thunk)))
499-
500-
;; ---------------------------------------------------------------------------------------------------
501-
;; context reduction
502-
503-
; Context reduction is the process of simplifying contexts by pruning unnecessary constraints.
504-
; Removing these constraints reduces the number of dictionaries that need to be passed, which is
505-
; especially important in macro-enabled Hackett, since users might write macros that expand to
506-
; constraints with redundant or otherwise unnecessary information. To avoid placing an unreasonable
507-
; burden on macro authors to intelligently prune these contexts themselves, Hackett guarantees it will
508-
; perform a certain amount of context reduction automatically.
509-
;
510-
; Implementing context reduction is a problem with a large design space, but fortunately, the various
511-
; choices and their tradeoffs have been covered extensively in the paper “Type classes: an exploration
512-
; of the design space”, available here:
513-
;
514-
; https://www.microsoft.com/en-us/research/wp-content/uploads/1997/01/multi.pdf
515-
;
516-
; Hackett implements context reduction based on the following rules:
517-
;
518-
; 1. Duplication constraint elimination. For example, the type:
519-
;
520-
; (forall [a] (Eq a) (Eq a) => ....)
521-
;
522-
; ...can be simplified to this type:
523-
;
524-
; (forall [a] (Eq a) => ....)
525-
;
526-
; 2. Superclass subsumption. Since subclass dictionaries include dictionaries for their
527-
; superclasses, superclass constraints can be eliminated in favor of equivalent subclass ones.
528-
; For example, the type:
529-
;
530-
; (forall [f] (Functor f) (Applicative f) => ....)
531-
;
532-
; ...can be simplified to this type:
533-
;
534-
; (forall [f] (Applicative f) => ....)
535-
;
536-
; 3. Discharging tautological constraints. A tautological constraint is a constraint that can be
537-
; immediately resolved via an in-scope instance declaration. For example, the constraint
538-
; (Eq Integer) always holds, so there is never any reason to include it in a context.
539-
;
540-
; This is complicated slightly by the fact that instances can also have contexts, so for a
541-
; constraint to be tautological, all constraints in the chosen instance context must also be
542-
; tautological. For example, given an instance:
543-
;
544-
; (instance (forall [a] (Eq a) => (Foo (Tuple a b))) ....)
545-
;
546-
; ...the constraint (Foo (Tuple Integer t)) is tautological regardless of t, since (Eq Integer)
547-
; is tautological.
548-
549-
(define/contract (constr-tautological? constr #:extra-constrs [extra-constrs '()])
550-
(->* [constr?] [#:extra-constrs (listof constr?)] boolean?)
551-
(or (ormap #{type=? constr %} extra-constrs)
552-
(match/values (lookup-instance! constr #:src #'here #:failure-thunk (λ () (values #f #f)))
553-
[[#f _] #f]
554-
[[_ subgoals] (andmap constr-tautological? subgoals)])))
555-
556-
(define/contract (superclasses-entail? constr-a constr-b)
557-
(-> constr? constr? boolean?)
558-
(match-let-values ([(class ts) (constr->class:info+ts constr-a)])
559-
(let* ([inst-dict (map cons (class:info-vars class) ts)]
560-
[supers (map #{insts % inst-dict} (class:info-superclasses class))])
561-
(or (ormap #{types-match?! % constr-b} supers)
562-
(ormap #{superclasses-entail? % constr-b} supers)))))
563-
564-
(define/contract (constrs-entail? constrs constr)
565-
(-> (listof constr?) constr? boolean?)
566-
(and (or (member constr constrs type=?)
567-
(ormap #{superclasses-entail? % constr} constrs))
568-
#t))
569-
570-
(define/contract (reduce-context constrs
571-
#:extra-tautological-constrs [extra-tautological-constrs '()])
572-
(->* [(listof constr?)] [#:extra-tautological-constrs (listof constr?)] (listof constr?))
573-
(let loop ([constrs constrs]
574-
[constrs* '()])
575-
(match constrs
576-
[(cons constr remaining-constrs)
577-
(loop remaining-constrs
578-
(let ([reduced-context (append remaining-constrs constrs*)])
579-
(if (or (constr-tautological?
580-
constr
581-
#:extra-constrs (append reduced-context extra-tautological-constrs))
582-
(constrs-entail? reduced-context constr))
583-
constrs*
584-
(cons constr constrs*))))]
585-
[_
586-
(reverse constrs*)])))
587-
588-
(define/contract type-reduce-context
589-
(-> type? type?)
590-
(syntax-parser
591-
#:context 'type-reduce-context
592-
[(~#%type:forall* [x ...] {~and t_qual (~#%type:qual* [constr ...] t)})
593-
#:with [constr* ...] (reduce-context (attribute constr))
594-
(quasisyntax/loc/props this-syntax
595-
(?#%type:forall* [x ...] #,(syntax/loc/props #'t_qual
596-
(?#%type:qual* [constr* ...] t))))]))
597-
598333
;; -------------------------------------------------------------------------------------------------
599334

600335
; To support type tooltips, we attach 'mouse-over-tooltips properties to syntax objects whenever we

0 commit comments

Comments
 (0)