Skip to content

Commit b29fae6

Browse files
authored
Merge pull request #72 from AlexKnauth/type-alias
Implement type aliases
2 parents 8e90c5a + 33f4d63 commit b29fae6

File tree

7 files changed

+202
-36
lines changed

7 files changed

+202
-36
lines changed

hackett-doc/scribblings/hackett/reference.scrbl

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ following combination of @racket[def], @racket[lambda], and @racket[case*]:
107107
The @racket[defn] form is generally preferred when defining top-level functions.
108108

109109
@(hackett-examples
110-
(defn square : (t:-> t:Integer t:Integer)
110+
(defn square : {t:Integer t:-> t:Integer}
111111
[[x] {x * x}])
112112
(eval:check (square 5) 25))}
113113

@@ -308,6 +308,47 @@ specified controls the fixity used by the associated @racket[type-constructor-id
308308
{(Leaf 1) :&: (Leaf 2) :&: (Leaf 3)})}
309309
@(close-eval data-examples-eval)
310310

311+
@subsection[#:tag "reference-type-alias"]{Defining type aliases}
312+
313+
@(define alias-examples-eval (make-hackett-eval))
314+
@defform[#:literals [left right]
315+
(type type-clause type-expr)
316+
#:grammar
317+
([type-clause name-id
318+
(code:line (name-id param-id ...+))]
319+
[maybe-fixity-ann (code:line #:fixity fixity)
320+
(code:line)]
321+
[fixity left right])]{
322+
323+
Defines a @deftech{type alias} named @racket[name-id]. Uses of @racket[name-id] are equivalent to
324+
uses of the type specified in @racket[type-expr]. If @racket[type-clause] is a bare @racket[name-id],
325+
then @racket[name-id] is bound directly to the type alias.
326+
327+
@(hackett-examples
328+
#:eval alias-examples-eval
329+
(type Num Double)
330+
(def n : Num 1.5)
331+
(#:type n))
332+
333+
If @racket[param-id]s are specified, then uses of the type alias must supply as many arguments as
334+
there are @racket[param-id]s. The arguments are supplied like those to a type constructor—i.e.
335+
@racket[(name-id type-argument ...)]—and the resulting type is @racket[type-expr] with each
336+
@racket[param-id] substituted with the corresponding @racket[type-argument].
337+
338+
Though the application of a type alias is syntactically similar to the application of a type
339+
constructor, type aliases are effectively type-level macros, and they may not be partially applied.
340+
All uses of a type alias must be fully saturated.
341+
342+
@(hackett-examples
343+
#:eval alias-examples-eval
344+
(type (Predicate a) {a t:-> t:Bool})
345+
(def zero? : (Predicate t:Integer) (== 0))
346+
(#:type zero?)
347+
(eval:check (zero? 0) True)
348+
(eval:check ((: zero? (Predicate t:Integer)) 0) True)
349+
(eval:error (: zero? Predicate)))
350+
@(close-eval alias-examples-eval)}
351+
311352
@subsection[#:tag "reference-numbers"]{Numbers}
312353

313354
@deftype[t:Integer]{

hackett-lib/hackett/base.rkt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#lang racket/base
22

33
(require (only-in hackett/private/adt case* case λ λ* lambda lambda* defn _)
4+
(only-in hackett/private/type-alias type)
45
(only-in hackett/private/class instance derive-instance)
56
(except-in hackett/private/kernel λ lambda)
67
hackett/private/provide
78
(only-in hackett/private/toplevel @%top-interaction))
89
(provide (all-from-out hackett/private/adt)
10+
(all-from-out hackett/private/type-alias)
911
(all-from-out hackett/private/class)
1012
(all-from-out hackett/private/kernel)
1113
(all-from-out hackett/private/provide)

hackett-lib/hackett/private/base.rkt

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,36 @@
187187
syntax-local-introduce)
188188
xs)])
189189
(for/lists [es- ts_es]
190-
([k (in-list ks)]
191-
[e (in-list (map car es+ts))]
192-
[e/elab (in-list es/elab)]
193-
[scoped-intdef-ctx (in-list scoped-intdef-ctxs)])
194-
(let* ([e- (local-expand e/elab 'expression stop-ids (if scoped-intdef-ctx
195-
(list intdef-ctx scoped-intdef-ctx)
196-
intdef-ctx))]
197-
[t_e (get-type e-)])
198-
(unless t_e (raise-syntax-error #f "no inferred type" e))
199-
(k (syntax-property e- 'disappeared-binding
200-
(cons (syntax-property e 'disappeared-binding) disappeared-bindings))
201-
t_e)))))
190+
([k (in-list ks)]
191+
[e (in-list (map car es+ts))]
192+
[e/elab (in-list es/elab)]
193+
[scoped-intdef-ctx (in-list scoped-intdef-ctxs)])
194+
(let* ([e- (let ([intdef-ctxs (if scoped-intdef-ctx
195+
(list intdef-ctx scoped-intdef-ctx)
196+
intdef-ctx)])
197+
(let loop ([e e/elab])
198+
(syntax-parse (local-expand e 'expression stop-ids intdef-ctxs)
199+
#:literals [#%expression]
200+
; Expand through #%expression forms if we don’t find an inferred type
201+
; immediately and hope that the nested expression will have a type.
202+
[(head:#%expression e*)
203+
#:when (not (get-type this-syntax))
204+
(syntax-track-origin (loop #'e*) this-syntax #'head)]
205+
; If we find a bare identifier, it’s either a variable, an out-of-context
206+
; identifier, or an unbound identifier that stopped expanding just before
207+
; introducing racket/base’s #%top (since that #%top is implicitly added to
208+
; the stop list). The former two cases are okay, but the latter is not, so
209+
; keep going to trigger the unbound identifier error if the identifier is
210+
; actually unbound.
211+
[_:id
212+
#:when (not (identifier-binding this-syntax))
213+
(local-expand this-syntax 'expression '() intdef-ctxs)]
214+
[_ this-syntax])))]
215+
[t_e (get-type e-)])
216+
(unless t_e (raise-syntax-error #f "no inferred type" e-))
217+
(k (syntax-property e- 'disappeared-binding
218+
(cons (syntax-property e 'disappeared-binding) disappeared-bindings))
219+
t_e)))))
202220

203221
; With everything inferred and checked, all that’s left to do is return the results.
204222
(values xs-* es- ts_es))

hackett-lib/hackett/private/infix.rkt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@
3535
(define (infix-operator? x)
3636
(and (has-prop:infix-operator? x)
3737
(operator-fixity? (infix-operator-fixity x))))
38-
38+
39+
; Infix transformer bindings; use the make-infix-variable-like-transformer constructor instead of
40+
; creating instances of this struct directly.
3941
(struct infix-variable-like-transformer (procedure fixity)
4042
#:property prop:procedure (struct-field-index procedure)
4143
#:property prop:infix-operator

hackett-lib/hackett/private/kernel.rkt

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -90,48 +90,45 @@
9090
(begin
9191
(define-syntax-parser @%app
9292
[(~parens _ . args)
93-
(syntax/loc this-syntax
94-
(@%app/prefix . args))]
93+
(datum->syntax this-syntax (cons #'@%app/prefix #'args) this-syntax)]
9594
[{~braces _ . args}
96-
(syntax/loc this-syntax
97-
(@%app/infix . args))])
95+
(datum->syntax this-syntax (cons #'@%app/infix #'args) this-syntax)])
9896

9997
(define-syntax-parser @%app/prefix
10098
[(_ f:expr) #'f]
10199
[(_ f:expr x:expr)
102-
(syntax/loc this-syntax
103-
(@%app1 f x))]
100+
(datum->syntax this-syntax (list #'@%app1 #'f #'x) this-syntax)]
104101
[(_ f:expr x:expr xs:expr ...)
105-
(quasisyntax/loc this-syntax
106-
(@%app/prefix #,(~> (syntax/loc this-syntax
107-
(@%app1 f x))
108-
(syntax-property 'omit-type-tooltip #t))
109-
xs ...))])
102+
#:with inner-app (~> (datum->syntax this-syntax (list #'@%app1 #'f #'x) this-syntax)
103+
(syntax-property 'omit-type-tooltip #t))
104+
(datum->syntax this-syntax (list* #'@%app/prefix #'inner-app #'(xs ...)) this-syntax)])
110105

111106
(define-syntax-parser @%app/infix
112107
[(_ a:expr op:infix-operator b:expr {~seq ops:infix-operator bs:expr} ...+)
113108
#:when (eq? 'left (attribute op.fixity))
114109
#:and ~!
115110
#:fail-unless (andmap #{eq? % 'left} (attribute ops.fixity))
116111
"cannot mix left- and right-associative operators in the same infix expression"
117-
(quasitemplate/loc this-syntax
118-
(@%app/infix #,(~> (syntax/loc this-syntax
119-
(@%app/infix a op b))
112+
#:with inner-app (~> (datum->syntax this-syntax (list #'@%app/infix #'a #'op #'b) this-syntax)
120113
(syntax-property 'omit-type-tooltip #t))
121-
{?@ ops bs} ...))]
114+
(~> (list* #'@%app/infix #'inner-app (syntax->list #'({?@ ops bs} ...)))
115+
(datum->syntax this-syntax _ this-syntax))]
122116
[(_ {~seq as:expr ops:infix-operator} ...+ a:expr op:infix-operator b:expr)
123117
#:when (eq? 'right (attribute op.fixity))
124118
#:and ~!
125119
#:fail-unless (andmap #{eq? % 'right} (attribute ops.fixity))
126120
"cannot mix left- and right-associative operators in the same infix expression"
127-
(quasitemplate/loc this-syntax
128-
(@%app/infix {?@ as ops} ...
129-
#,(~> (syntax/loc this-syntax
130-
(@%app/infix a op b))
131-
(syntax-property 'omit-type-tooltip #t))))]
121+
#:with inner-app (~> (datum->syntax this-syntax (list #'@%app/infix #'a #'op #'b) this-syntax)
122+
(syntax-property 'omit-type-tooltip #t))
123+
(~> (append (list #'@%app/infix) (syntax->list #'({?@ as ops} ...)) (list #'inner-app))
124+
(datum->syntax this-syntax _ this-syntax))]
132125
[(_ a:expr op:expr b:expr)
133-
(syntax/loc this-syntax
134-
(@%app/prefix op a b))]
126+
(quasisyntax/loc this-syntax
127+
(#%expression
128+
#,(~> (datum->syntax this-syntax (list #'op #'a #'b) this-syntax)
129+
; Explicitly make 'paren-shape #f on the new application form to avoid the #\{ value
130+
; being copied onto the prefix application when #%expression is expanded.
131+
(syntax-property 'paren-shape #f))))]
135132
[(_ a:expr)
136133
#'a]))))
137134

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#lang curly-fn racket/base
2+
3+
(require (for-syntax racket/base
4+
racket/format
5+
syntax/intdef
6+
threading
7+
8+
hackett/private/infix
9+
hackett/private/typecheck
10+
hackett/private/util/stx)
11+
syntax/parse/define
12+
13+
(only-in hackett/private/adt type-constructor-spec))
14+
15+
(provide type)
16+
17+
(begin-for-syntax
18+
; Alias transformer bindings; use the make-alias-transformer constructor instead of creating
19+
; instances of this struct directly.
20+
(struct alias-transformer (procedure fixity)
21+
#:property prop:procedure (struct-field-index procedure)
22+
#:property prop:infix-operator
23+
(λ (self) (alias-transformer-fixity self)))
24+
25+
(define (make-alias-transformer args type-template fixity)
26+
(let ([arity (length args)])
27+
(alias-transformer
28+
(cond
29+
[(zero? arity)
30+
(make-variable-like-transformer type-template)]
31+
[else
32+
(syntax-parser
33+
[(head:id t:type ...)
34+
#:fail-unless (= (length (attribute t)) arity)
35+
(~a "expected " arity " argument(s) to type alias, got "
36+
(length (attribute t)))
37+
(for/fold ([result (insts type-template (map cons args (attribute t)))])
38+
([residual (in-list (attribute t.residual))])
39+
(syntax-track-origin result residual #'head))]
40+
[:id
41+
#:fail-when #t (~a "expected " arity " argument(s) to type alias")
42+
(error "unreachable")])])
43+
fixity))))
44+
45+
46+
(define-syntax-parser type
47+
[(_ ctor-spec:type-constructor-spec {~type type-template:expr})
48+
#:with [ctor-spec*:type-constructor-spec] (type-namespace-introduce #'ctor-spec)
49+
#:with fixity (attribute ctor-spec.fixity)
50+
51+
; Create a dummy internal definition context with args.
52+
#:do [(define intdef-ctx (syntax-local-make-definition-context))
53+
(syntax-local-bind-syntaxes (attribute ctor-spec*.arg) #f intdef-ctx)]
54+
#:with [arg* ...] (map #{internal-definition-context-introduce intdef-ctx %}
55+
(attribute ctor-spec*.arg))
56+
57+
; Expanding the type in `ctx` checks immediately that it is a valid type,
58+
; rather than deferring that check to when the type alias is applied.
59+
#:with {~var type-template- (type intdef-ctx)} #'type-template
60+
(~>> #'(begin
61+
(define-values [] type-template-.residual)
62+
(define-syntax ctor-spec*.tag
63+
(make-alias-transformer
64+
(list (quote-syntax arg*) ...)
65+
(quote-syntax type-template-.expansion)
66+
'fixity)))
67+
(internal-definition-context-track intdef-ctx))])
68+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#lang hackett
2+
3+
(require hackett/private/test
4+
(only-in racket/base submod)
5+
(submod tests/hackett/typecheck assertions))
6+
7+
(type X Integer)
8+
(def x : X 5)
9+
(typecheck-fail (: "" X))
10+
11+
(type (Arr a b) {a -> b})
12+
(type (Pred a) (Arr a Bool))
13+
(type (BiRel a) {a -> a -> Bool})
14+
15+
(type Y (forall [a b] (Monoid b) => (Either a b)))
16+
17+
(typecheck-fail
18+
(λ ([x : (Arr Bool)]) ; not enough args to alias
19+
x))
20+
21+
(defn never : (forall [a] (Pred a))
22+
[[x] False])
23+
24+
(test {(never 5) ==! False})
25+
(test {(never "asdasaf") ==! False})
26+
27+
(def int= : (BiRel Integer)
28+
==)
29+
30+
(test {{4 int= 6} ==! False})
31+
(test {{4 int= 4} ==! True})
32+
33+
(type {a ~> b} #:fixity right {a -> (Maybe b)})
34+
35+
(def head* : (forall [a] {(List a) ~> a}) head)
36+
37+
(test {(head* {1 :: Nil}) ==! (Just 1)})
38+
(test {(head* {Nil : (List Integer)}) ==! Nothing})

0 commit comments

Comments
 (0)