typed-map.scrbl (4958B)
1 #lang scribble/manual 2 @require[scribble/example 3 @for-label[typed-map]] 4 5 @(module orig racket/base 6 (require scribble/manual 7 (for-label racket/base)) 8 (provide orig:map orig:foldl orig:foldr) 9 (define orig:map @racket[map]) 10 (define orig:foldl @racket[foldl]) 11 (define orig:foldr @racket[foldr])) 12 @(require 'orig) 13 14 @title{Type inference helper for @orig:map} 15 @author[@author+email["Suzanne Soy" "racket@suzanne.soy"]] 16 17 @defmodule[typed-map] 18 19 @defproc[#:kind "syntax" 20 (map [f (→ A ... B)] [l (Listof A)] ...) (Listof B)]{ 21 Like @orig:map from @racketmodname[typed/racket/base], but with better type 22 inference for Typed Racket. 23 24 When @racket[f] is a literal lambda of the form 25 @racket[(λ (arg ...) body ...)], it is not necessary to specify the type of 26 the arguments, as they will be inferred from the list. 27 28 @examples[#:eval ((make-eval-factory '(typed-map) #:lang 'typed/racket)) 29 (map (λ (x) (* x 2)) '(1 2 3)) 30 (let ([l '(4 5 6)]) 31 (map (λ (x) (* x 2)) l))] 32 33 This enables the use of @racket[#,(hash-lang) #,(racketmodname aful)] for 34 @racket[map] in Typed Racket. 35 36 Furthermore, when @racket[f] is a polymorphic function, type annotations are 37 not needed: 38 39 @examples[#:eval ((make-eval-factory '(typed-map) #:lang 'typed/racket)) 40 (map car '([a . 1] [b . 2] [c . 3]))] 41 42 Compare this with the behaviour of @orig:map from 43 @racketmodname[racket/base], which generates a type error: 44 45 @examples[#:eval ((make-eval-factory '() #:lang 'typed/racket)) 46 (eval:alts (#,orig:map car '([a . 1] [b . 2] [c . 3])) 47 (eval:error (map car '([a . 1] [b . 2] [c . 3]))))] 48 49 When used as an identifier, the @racket[map] macro expands to the original 50 @orig:map from @racketmodname[typed/racket/base]: 51 52 @examples[#:eval ((make-eval-factory '(typed-map) #:lang 'typed/racket)) 53 (eval:alts (require (only-in racket/base [#,orig:map orig:map])) 54 (require (only-in racket/base [map orig:map]))) 55 (equal? map orig:map)] 56 57 Note that the implementation expands to a large expression, and makes use of 58 @racket[set!] internally to build the result list. The trick used proceeds as 59 follows: 60 @itemlist[ 61 @item{It uses @racket[(reverse (reverse l))] to generalise the type of the 62 list, without having to express that type, so that Type / Racket infers a 63 more general type of the form @racket[(Listof A)], without detecting that the 64 output is identical to the input. An unoptimizable guard prevents the 65 double-reverse from actually being executed, so it does not incur a 66 performance cost.} 67 @item{It uses a 68 @seclink["Named_let" #:doc '(lib "scribblings/guide/guide.scrbl")]{named 69 @racket[let]} to perform the loop. The function @racket[f] is never passed 70 as an argument to another polymorphic function, and is instead directly 71 called with the appropriate arguments. The error message ``Polymorphic 72 function `map' could not be applied to arguments'' is therefore not raised.} 73 @item{To have the most precise and correct types, it uses a named let with a 74 single variable containing the list (with the generalized type). An outer let 75 binds a mutable accumulator, initialized with a single-element list 76 containing the result of applying @racket[f] on the first element of the 77 list. Since all elements of the list belong to the generalized type, the 78 result of calling @racket[f] on any element has the same type, therefore the 79 accumulator has the type @racket[(Listof B)], where @racket[B] is the 80 inferred type of the result of @racket[f].}]} 81 82 83 @defproc[#:kind "syntax" 84 (foldl [f (→ A ... Acc Acc)] [init Acc] [l (Listof A)] ...) Acc]{ 85 Like @orig:foldl from @racketmodname[typed/racket/base] but with better type 86 inference for Typed Racket. 87 88 This form is implemented in the same way as the overloaded version of 89 @racket[map] presented above. 90 91 Note that in some cases, the type for the accumulator is not generalised 92 enough based on the result of the first iteration, in which cases annotations 93 are needed: 94 95 @examples[#:eval ((make-eval-factory '(typed-map) #:lang 'typed/racket)) 96 (eval:error (foldl (λ (x acc) (cons acc (add1 x))) '() '(1 2 3))) 97 (foldl (λ (x [acc : (Rec R (U Null (Pairof R Positive-Index)))]) 98 (cons acc (add1 x))) 99 '() 100 '(1 2 3))]} 101 102 @defproc[#:kind "syntax" 103 (foldr [f (→ A ... Acc Acc)] [init Acc] [l (Listof A)] ...) Acc]{ 104 Like @orig:foldr from @racketmodname[typed/racket/base] but with better type 105 inference for Typed Racket. 106 107 This form is implemented in the same way as the overloaded version of 108 @racket[map] presented above. 109 110 Note that in some cases, the type for the accumulator is not generalised 111 enough based on the result of the first iteration, in which cases annotations 112 are needed. See the example given for @racket[foldl].}