Add a TR Guide section on occurrence typing
This commit is contained in:
parent
cbfa7ac0ea
commit
09559e43bd
|
@ -0,0 +1,142 @@
|
||||||
|
#lang scribble/manual
|
||||||
|
|
||||||
|
@begin[(require "../utils.rkt"
|
||||||
|
scribble/core scribble/eval
|
||||||
|
(for-label (only-meta-in 0 typed/racket)))]
|
||||||
|
|
||||||
|
@(define the-eval (make-base-eval))
|
||||||
|
@(the-eval '(require typed/racket))
|
||||||
|
|
||||||
|
@title[#:tag "occurrence"]{Occurrence Typing}
|
||||||
|
|
||||||
|
@section{Basic Occurrence Typing}
|
||||||
|
|
||||||
|
One of Typed Racket's distinguishing type system features is
|
||||||
|
@deftech{occurrence typing}, which allows the type system to ascribe
|
||||||
|
more precise types based on whether a predicate check succeeds or
|
||||||
|
fails.
|
||||||
|
|
||||||
|
To illustrate, consider the following code:
|
||||||
|
|
||||||
|
@racketblock+eval[#:eval the-eval
|
||||||
|
(: flexible-length ((U String (Listof Any)) -> Integer))
|
||||||
|
(define (flexible-length str-or-lst)
|
||||||
|
(if (string? str-or-lst)
|
||||||
|
(string-length str-or-lst)
|
||||||
|
(length str-or-lst)))
|
||||||
|
]
|
||||||
|
|
||||||
|
The @racket[_flexible-length] function above computes the length of
|
||||||
|
either a string or a list. The function body uses the typical Racket
|
||||||
|
idiom of dispatching using a predicate (e.g., @racket[string?]).
|
||||||
|
|
||||||
|
Typed Racket successfully type-checks this function because the type
|
||||||
|
system understands that in the "then" branch of the @racket[if]
|
||||||
|
expression, the predicate @racket[string?] must have returned a true
|
||||||
|
value. The type system further knows that if @racket[string?]
|
||||||
|
returns true, then the @racket[_str-or-lst] variable must have type
|
||||||
|
@racket[String] and can narrow the type from its original union of
|
||||||
|
@racket[String] and @racket[(Listof Any)]. This allows the call to
|
||||||
|
@racket[string-length] in the "then" branch to type-check
|
||||||
|
successfully.
|
||||||
|
|
||||||
|
Furthermore, the type system also knows that in the "else" branch of
|
||||||
|
the @racket[if] expression, the predicate must have returned
|
||||||
|
@racket[#f]. This implies that the variable @racket[_str-or-lst] must
|
||||||
|
have type @racket[(Listof Any)] by process of elimination, and thus
|
||||||
|
the call @racket[(length str-or-lst)] type-checks.
|
||||||
|
|
||||||
|
To summarize, if Typed Racket can determine the type a variable must
|
||||||
|
have based on a predicate check in a conditional expression, it can
|
||||||
|
narrow the type of the variable within the appropriate branch of the
|
||||||
|
conditional.
|
||||||
|
|
||||||
|
@section{Filter and Predicates}
|
||||||
|
|
||||||
|
In the previous section, we demonstrated that a Typed Racket programmer
|
||||||
|
can take advantage of occurrence typing to type-check functions
|
||||||
|
with union types and conditionals. This may raise the question: how
|
||||||
|
does Typed Racket know how to narrow the type based on the predicate?
|
||||||
|
|
||||||
|
The answer is that predicate types in Typed Racket are annotated
|
||||||
|
with @deftech{filters} that tell the typechecker what additional
|
||||||
|
information is gained when a predicate check succeeds or fails.
|
||||||
|
|
||||||
|
For example, consider the REPL's type printout for @racket[string?]:
|
||||||
|
|
||||||
|
@interaction[#:eval the-eval string?]
|
||||||
|
|
||||||
|
The type @racket[(Any -> Boolean : String)] has three parts. The first
|
||||||
|
two are the same as any other function type and indicate that the
|
||||||
|
predicate takes any value and returns a boolean. The third part, after
|
||||||
|
the @racket[_:], is a @tech{filter} that tells the typechecker two
|
||||||
|
things:
|
||||||
|
|
||||||
|
@itemlist[#:style 'ordered
|
||||||
|
@item{If the predicate check succeeds, the argument variable has type @racket[String]}
|
||||||
|
@item{If the predicate check fails, the argument variable @emph{does not} have type @racket[String]}
|
||||||
|
]
|
||||||
|
|
||||||
|
Predicates for all built-in types are annotated with similar filters
|
||||||
|
that allow the type system to reason about predicate checks.
|
||||||
|
|
||||||
|
@section{Other conditionals and assertions}
|
||||||
|
|
||||||
|
@margin-note{After all, these control flow constructs macro-expand
|
||||||
|
to @racket[if] in the end.}
|
||||||
|
|
||||||
|
So far, we have seen that occurrence typing allows precise reasoning
|
||||||
|
about @racket[if] expressions. Occurrence typing works for most
|
||||||
|
control flow constructs that are present in Racket such as
|
||||||
|
@racket[cond], @racket[when], and others.
|
||||||
|
|
||||||
|
For example, the @racket[_flexible-length] function from earlier can
|
||||||
|
be re-written to use @racket[cond] with no additional effort:
|
||||||
|
|
||||||
|
@racketblock+eval[#:eval the-eval
|
||||||
|
(: flexible-length/cond ((U String (Listof Any)) -> Integer))
|
||||||
|
(define (flexible-length/cond str-or-lst)
|
||||||
|
(cond [(string? str-or-lst) (string-length str-or-lst)]
|
||||||
|
[else (length str-or-lst)]))
|
||||||
|
]
|
||||||
|
|
||||||
|
In some cases, the type system does not have enough information or is
|
||||||
|
too conservative to typecheck an expression. For example, consider
|
||||||
|
the following interaction:
|
||||||
|
|
||||||
|
@interaction[#:eval the-eval
|
||||||
|
(: a Positive-Integer)
|
||||||
|
(define a 15)
|
||||||
|
(: b Positive-Integer)
|
||||||
|
(define b 20)
|
||||||
|
(: c Positive-Integer)
|
||||||
|
(define c (- b a))
|
||||||
|
]
|
||||||
|
|
||||||
|
In this case, the type system only knows that @racket[_a] and
|
||||||
|
@racket[_b] are positive integers and cannot conclude that their
|
||||||
|
difference will always be positive in defining @racket[_c]. In cases
|
||||||
|
like this, occurrence typing can be used to make the code type-check
|
||||||
|
using an @emph{assertion}. For example,
|
||||||
|
|
||||||
|
@racketblock+eval[#:eval the-eval
|
||||||
|
(: d Positive-Integer)
|
||||||
|
(define d (assert (- b a) positive?))
|
||||||
|
]
|
||||||
|
|
||||||
|
Using the filter on @racket[positive?], Typed Racket can assign the
|
||||||
|
type @racket[Positive-Integer] to the whole @racket[assert]
|
||||||
|
expression. This type-checks, but note that the assertion may raise
|
||||||
|
an exception at run-time if the predicate returns @racket[#f].
|
||||||
|
|
||||||
|
Note that @racket[assert] is a derived concept in Typed Racket and is
|
||||||
|
a natural consequence of occurrence typing. The assertion above is
|
||||||
|
essentially equivalent to the following:
|
||||||
|
|
||||||
|
@racketblock+eval[#:eval the-eval
|
||||||
|
(: e Positive-Integer)
|
||||||
|
(define e (let ([diff (- b a)])
|
||||||
|
(if (positive? diff)
|
||||||
|
diff
|
||||||
|
(error "Assertion failed"))))
|
||||||
|
]
|
|
@ -20,6 +20,7 @@ with Racket. For an introduction to Racket, see the @(other-manual '(lib "scrib
|
||||||
@include-section["guide/begin.scrbl"]
|
@include-section["guide/begin.scrbl"]
|
||||||
@include-section["guide/more.scrbl"]
|
@include-section["guide/more.scrbl"]
|
||||||
@include-section["guide/types.scrbl"]
|
@include-section["guide/types.scrbl"]
|
||||||
|
@include-section["guide/occurrence.scrbl"]
|
||||||
@include-section["guide/typed-untyped-interaction.scrbl"]
|
@include-section["guide/typed-untyped-interaction.scrbl"]
|
||||||
@include-section["guide/optimization.scrbl"]
|
@include-section["guide/optimization.scrbl"]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user