www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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].}