typed-map/typed-map-doc/scribblings/typed-map.scrbl

112 lines
4.8 KiB
Racket

#lang scribble/manual
@require[scribble/example
@for-label[typed-map]]
@title{typed-map}
@author[@author+email["Georges Dupéron" "georges.duperon@gmail.com"]]
@(module orig racket/base
(require scribble/manual
(for-label racket/base))
(provide orig:map orig:foldl orig:foldr)
(define orig:map @racket[map])
(define orig:foldl @racket[foldl])
(define orig:foldr @racket[foldr]))
@(require 'orig)
@defmodule[typed-map]
@defproc[#:kind "syntax"
(map [f ( A ... B)] [l (Listof A)] ...) (Listof B)]{
Like @orig:map from @racketmodname[typed/racket/base], but with better type
inference for Typed Racket.
When @racket[f] is a literal lambda of the form
@racket[(λ (arg ...) body ...)], it is not necessary to specify the type of
the arguments, as they will be inferred from the list.
@examples[#:eval ((make-eval-factory '(typed-map) #:lang 'typed/racket))
(map (λ (x) (* x 2)) '(1 2 3))
(let ([l '(4 5 6)])
(map (λ (x) (* x 2)) l))]
This enables the use of @racket[#,(hash-lang) #,(racketmodname aful)] for
@racket[map] in Typed Racket.
Furthermore, when @racket[f] is a polymorphic function, type annotations are
not needed:
@examples[#:eval ((make-eval-factory '(typed-map) #:lang 'typed/racket))
(map car '([a . 1] [b . 2] [c . 3]))]
Compare this with the behaviour of @orig:map from
@racketmodname[racket/base], which generates a type error:
@examples[#:eval ((make-eval-factory '() #:lang 'typed/racket))
(eval:alts (#,orig:map car '([a . 1] [b . 2] [c . 3]))
(eval:error (map car '([a . 1] [b . 2] [c . 3]))))]
When used as an identifier, the @racket[map] macro expands to the original
@orig:map from @racketmodname[typed/racket/base]:
@examples[#:eval ((make-eval-factory '(typed-map) #:lang 'typed/racket))
(eval:alts (require (only-in racket/base [#,orig:map orig:map]))
(require (only-in racket/base [map orig:map])))
(equal? map orig:map)]
Note that the implementation expands to a large expression, and makes use of
@racket[set!] internally to build the result list. The trick used proceeds as
follows:
@itemlist[
@item{It uses @racket[(reverse (reverse l))] to generalise the type of the
list, without having to express that type, so that Type / Racket infers a
more general type of the form @racket[(Listof A)], without detecting that the
output is identical to the input. An unoptimizable guard prevents the
double-reverse from actually being executed, so it does not incur a
performance cost.}
@item{It uses a
@seclink["Named_let" #:doc '(lib "scribblings/guide/guide.scrbl")]{named
@racket[let]} to perform the loop. The function @racket[f] is never passed
as an argument to another polymorphic function, and is instead directly
called with the appropriate arguments. The error message ``Polymorphic
function `map' could not be applied to arguments'' is therefore not raised.}
@item{To have the most precise and correct types, it uses a named let with a
single variable containing the list (with the generalized type). An outer let
binds a mutable accumulator, initialized with a single-element list
containing the result of applying @racket[f] on the first element of the
list. Since all elements of the list belong to the generalized type, the
result of calling @racket[f] on any element has the same type, therefore the
accumulator has the type @racket[(Listof B)], where @racket[B] is the
inferred type of the result of @racket[f].}]}
@defproc[#:kind "syntax"
(foldl [f ( A ... Acc Acc)] [init Acc] [l (Listof A)] ...) Acc]{
Like @orig:foldl from @racketmodname[typed/racket/base] but with better type
inference for Typed Racket.
This form is implemented in the same way as the overloaded version of
@racket[map] presented above.
Note that in some cases, the type for the accumulator is not generalised
enough based on the result of the first iteration, in which cases annotations
are needed:
@examples[#:eval ((make-eval-factory '(typed-map) #:lang 'typed/racket))
(eval:error (foldl (λ (x acc) (cons acc (add1 x))) '() '(1 2 3)))
(foldl (λ (x [acc : (Rec R (U Null (Pairof R Positive-Index)))])
(cons acc (add1 x)))
'()
'(1 2 3))]}
@defproc[#:kind "syntax"
(foldr [f ( A ... Acc Acc)] [init Acc] [l (Listof A)] ...) Acc]{
Like @orig:foldr from @racketmodname[typed/racket/base] but with better type
inference for Typed Racket.
This form is implemented in the same way as the overloaded version of
@racket[map] presented above.
Note that in some cases, the type for the accumulator is not generalised
enough based on the result of the first iteration, in which cases annotations
are needed. See the example given for @racket[foldl].}