let
2.1.3Functions
λ
+
-
*
/
car
cdr
map
glb
lub
φ
φ?
9.0
top
← prev up next →

Trivial: type-tailored library functionsπŸ”— i

(require trivial ) package: trivial

The trivial module exports tailored versions of standard library functions.

1What is Tailoring?πŸ”— i

A tailored function f+:
  • has the same specification and behavior as some library function f

  • but can catch runtime errors during expansion

  • and may typecheck a little smoother in Typed Racket.

1.1Examples of TailoringπŸ”— i

For example, make a new Racket module with a malformed call to format :

 
(format "batmanand~a")

This file will compile no problem (raco make file.rkt), but will raise a runtime error (racket file.rkt). If you add the line (require trivial), the file no longer compiles, but instead gives a helpful error message.

Here’s another example. Save the following (correct) Racket module:

 
(define (get-sidekickstr)
(cond
[(regexp-match #rx"^([a-z]+)and([a-z]+)$"str)
[else "???"]))
 
(get-sidekick"batmanandalfred")

It should compile and run, returning "alfred". Great. Now try converting it to Typed Racket:

 
(define (get-sidekick(str:String )):String
(cond
[(regexp-match #rx"^([a-z]+)and([a-z]+)$"str)
[else "???"]))
 
(get-sidekick"batmanandalfred")

The file doesn’t compile anymore. Adding (require trivial) to the top of the file removes the error.

1.2Technical DescriptionπŸ”— i

A tailored function f+ is really a macro that examines its call site and attempts to combine knowledge about the behavior of f with static properties of its arguments. If all goes well, the tailoring will either (1) identify an error or (2) transform the call site into an equivalent—but more efficient or more Typed-Racket-friendly—call. Otherwise, the call to f+ behaves exactly as a call to f would.

In general, the static properties could be the result of any static analysis. But this library is limited to properties that other macros can establish through local analysis and propagate via syntax properties. See Defining new Tailorings for more details.

2Using TailoringsπŸ”— i

The following tailorings are all provided by the trivial module. Note that these tailorings have the same name as their Racket and Typed Racket equivalents to make this library a drop-in replacement for existing code.

The descriptions below assume familiarity with the Racket reference and describe only the new behavior of the tailored function or form. Click the name of any tailoring to see its definition in the Racket reference.

2.1Built-in TailoringsπŸ”— i

2.1.1DefinitionsπŸ”— i

WARNING the static analysis implemented by trivial/define is unsound in the presence of set! . Do not set! in a module that uses trivial/define.

syntax

( define idexpr)

syntax

(define(headargs)expr)

Forces local expansion of expr and associates any inferred properties with id for the rest of expansion.

syntax

( let ([idval-expr]...)body...+)

syntax

( let* ([idval-expr]...)body...+)

Forces local expansion of each val-expr and associates any inferred properties with the respective id in the context of body...+.

2.1.2Format StringsπŸ”— i

procedure

( format formv...)string?

form:string?
v:any/c

procedure

( printf formv...)void?

form:string?
v:any/c
When the string form is available during expansion, checks the number of values v... against the number of formatting escapes in form that require arguments. When used in a Typed Racket module, annotates each v with the type required by the corresponding formatting escape.

2.1.3FunctionsπŸ”— i

syntax

( curry f)

This form does not have a Typed Racket equivalent.

When the arity of the procedure f is available during expansion, expands to a curried version of f. In other words, if f is a function of N arguments then (curry f) is a chain of N one-argument functions.

For example,

(curry (λ (xyz)(+ (* xy)z)))

behaves the same as:
(λ (x)
(λ (y)
(λ (z)
(+ (* xy)z))))

syntax

( lambda (id...)body)

syntax

( λ (id...)body)

Expands to an anonymous function with known arity. Other tailorings can access this arity.

2.1.4Integer ArithmeticπŸ”— i

procedure

( + n...)integer?

procedure

( - n...)integer?

procedure

( * n...)integer?

Constant-folding arithmetic functions. When all arguments n... have integer values available during expansion, expands to a constant integer (or bignum). When only some arguments have available values, reduces the expression accordingly.

procedure

( / n...)real?

procedure

( quotient n...)integer?

Constant-folding division. Raises a syntax error "division by zero" when the final argument is zero? during expansion.

procedure

( add1 n)integer?

procedure

( sub1 n)integer?

Increment and decrement functions that propagate the value of their argument.

procedure

( expt n1n2)integer?

Constant-folding exponentiation. If the value of n1 is unknown, checks whether the value of n2 is zero? or a small constant. In the latter case, unfolds to repeated multiplication of n1.

2.1.5List OperationsπŸ”— i

procedure

( make-listkv)list?

v:any/c

procedure

( cons ad)pair?

a:any/c
d:any/c

procedure

( car p)any/c

p:pair?

procedure

( cdr p)any/c

p:pair?

procedure

( list v...)list?

v:any/c
lst:list?

procedure

( list-ref lstpos)any/c

lst:pair?

procedure

( list-tail lstpos)any/c

lst:pair?

procedure

( append lst...)list?

lst:list?

procedure

( reverse lst)list?

lst:list?

procedure

( map proclst...)list?

proc:procedure?
lst:list?

procedure

( sort lstless-than?)list?

lst:list?
less-than?:(-> any/c any/c any/c )
Length-aware and content-aware list operations. Operations that build lists propagate the length of their arguments. Operations that access lists check for bounds errors and propagate information about cells within a list.

Higher-order list functions check the arity of their functional argument; in particular, map includes a static check that the arity of its first argument includes the number of lists supplied at the call-site.

These Typed Racket examples demonstrate terms that would not typecheck without the trivial library.

Examples:
> (ann(- (length '(123))3)Zero)

- : Integer [more precisely: Zero]

0

> (ann(list-ref (make-list50)2)Zero)

- : Integer [more precisely: Zero]

0

> (ann(list-ref (list-ref '((A))0)0)'A)

- : 'A

'A

2.1.6Regular ExpressionsπŸ”— i

procedure

( regexp str)regexp?

str:string?

procedure

( pregexp str)pregexp?

str:string?

procedure

( byte-regexp byt)byte-regexp?

byt:bytes?

procedure

( byte-pregexp byt)byte-pregexp?

byt:bytes?
Regexp constructors; when their argument value is known during expansion, these constructors record the number of groups specified by the argument.

procedure

( regexp-match patterninput)

(if (and (or (string? pattern)(regexp? pattern))
(or (string? input)(path? input)))
When possible, the type of the result list (in the case of a successful match) matches the number of groups in pattern.

This example is adapted from scribble/html-render

Examples:
> (:parse-font-size:String-> (ListStringString(U#fString)String))
> (define (parse-font-sizestr)
(or (regexp-match #rx"^([0-9]*\\.?([0-9]+)?)(em|ex|pt|%|)$"str)
(error 'malformed-input)))

2.1.7String OperationsπŸ”— i

procedure

( bytes-ref bstrk)byte?

bstr:bytes?

procedure

( subbytes bstrstart[end])bytes?

bstr:bytes?

procedure

( bytes-append bstr...)bytes?

bstr:bytes?
String and byte string operations that track the value of their arguments.

Example:
> (regexp-match (string-append "(""a*"")")"aaa")

- : (U (List String String) False)

'("aaa" "aaa")

2.1.8Vector OperationsπŸ”— i

procedure

( vector v...)vector?

v:any/c

procedure

( vector-appendvec...)vector?

vec:vector?

procedure

( vector-ref vecpos)any/c

vec:vector?

procedure

( vector-set! vecposv)vector?

vec:vector?
v:any/c

procedure

( vector-mapprocvec...)vector?

proc:procedure?
vec:vector?

procedure

( vector-map!procvec...)void?

proc:procedure?
vec:vector?

procedure

( vector->list vec)list?

vec:vector?

procedure

( vector->immutable-vector vec)vector?

vec:vector?

procedure

( vector-fill! vecv)void?

vec:vector?
v:any/c

procedure

( vector-takevecpos)vector?

vec:vector?

procedure

( vector-take-rightvecpos)vector?

vec:vector?

procedure

( vector-dropvecpos)vector?

vec:vector?

procedure

( vector-drop-rightvecpos)vector?

vec:vector?
Length-aware and content-aware vector operations.

2.2Typed / Untyped InteractionπŸ”— i

The macros provided by trivial and related submodules are all untyped, but should work with no problems in Typed Racket modules. Under the hood, these macros keep two copies of every tailored identifier and use syntax-local-typed-context? to choose the appropriate identifiers and whether to expand to type-annotated code.

3Defining new TailoringsπŸ”— i

The bindings documented in this section are not provided by the trivial module.

Defining a new tailoring is not easy, but you can technically do it without making a pull request to this library. There are three steps to making a tailoring:
  • identify a property of Racket expressions,

  • define when & how to infer this property from #%datum literals,

  • define a set of tailorings that infer, propagate, and use the property

For example, here is a tailoring for add1 . If the value of its argument is known during expansion, it expands to an integer.

trivial/tailoring
(for-syntax racket/base)
(only-in racket/base[add1 λ-add1])
(only-in typed/racket/base[add1 τ-add1]))
 
(define-tailoring (-add1[e~>e+(φ [integer-domain i])])
#:with+add1(τλ #' τ-add1#' λ-add1)
(+add1e+)
#:+#t
' #, (+ i1)

Here is how to read the un-hygienic define-tailoring form:
  • -add1 is a macro expecting one argument, named e. (If -add1 is called with zero or +2 arguments, it expands to Racket’s add1 .)

  • Note: the key integer-domain describes the relationship between e and i. In short, if tailorings that interact with integer-domain are implemented correctly, then expanding e yields and expression e+ with value i associated with the key integer-domain if and only if all run-time values for e are integers with value i.

    -add1 expands its argument e to the term e+ and retrieves a dictionary φ of tailoring properties (see Tailoring Properties) associated with e+. It furthermore pattern-matches the dictionary φ to get the integer value i of the expression e+.

  • -add1 conditionally produces one of two syntax objects.
    • In the neutral case (#:=), when i is an indeterminate value, the result is a call to Racket’s (or Typed Racket’s) add1 .

    • In the positive case (#:+), when i is known during expansion, the result is the integer (+ i1)

  • Any syntax object produced by expanding a call to -add1 with one argument has a new dictionary of tailoring properties (#:φ). The dictionary has one key, integer-domain . The corresponding value is "the indeterminate integer" when the value of i is indeterminate and (+ i1) otherwise.

Here is the general form of define-tailoring :

syntax

( define-tailoring (tailored-idarg-pattern...)
guard...
defn...
#:=test-exprtemplate
#:+then-exprtemplate
#:-then-exprexpr
#:φprop-expr)
arg-pattern = (e-idelab->e+-id(φ-id[key-idmap->val-id]...))
guard = #:withexprexpr
| #:whenexpr
| #:fail-unlessexprexpr
defn = (define. expr)
test-expr = expr
prop-expr = expr
elab-> = ~>
|
map-> = ->
|
Defines a tailoring tailored-id.

The arg-pattern clauses describe the shape of arguments to tailored-id. These say: expand e-id to e+-id, retrieve the tailoring properties φ-id associated with e+-id, and finally retrieve the values val-id associated with the given key-id.

If an arg-pattern is followed by a literal ... , then the tailoring accepts any number of arguments and matches each against arg-pattern. See the example for map below.

The guard expressions are a subset of the guards recognized by syntax-parse . Guards are most useful for defining variables based on the context in which the macro is expanding; see for example the #:with guard in -add1.

The defn expressions introduce local bindings.

The reason for #:+ etc. is so that define-tailoring can (1) log 'info messages for successful (#:+) and unsuccessful (#:=) tailorings (2) wrap each template in a syntax/loc based on use-sites of the tailoring.

The #:=, #:+, and #:- clauses are a cond -like domain-specific language. Their order matters. The test-expr are typically expressions involving the val-id bound above. In general, the template forms describe how the tailoring produces code (see syntax for their grammar). But the specific meaning of each template should correspond to its keyword:
  • #:=test-exprtemplate uses test-expr to check whether the tailoring does not have enough information to transform the program. The template should implement "the standard Racket behavior" for tailored-id.

  • #:+test-exprtemplate determines when the tailoring can transform the program. The template may use unsafe operations, additional type annotations, or partial evaluation.

  • #:-test-exprexpr detects errors statically and invokes expr to raise an exception. This clause is optional; when omitted, raises an internal exn:fail? about the inexhaustive tailoring.

Finally, prop-expr describes the result with a new dictionary of tailoring properties.

Note if any val-id is bound to a "top element" of the domain key-id, then define-tailoring raises an error message. See Property Domains for further intuition.

These examples contain free variables, but should convey the main ideas of define-tailoring . See the library implementation for working versions.

Example: a vector-length that computes the length statically.
(define-tailoring (-vector-length[e~>e+(φ [vector-domain v])])
#:with+vl(τλ #' τ-vector-length#' λ-vector-length)
#:=(⊥? V-domv)
(+vle+)
#:+#t' #, n

Example: a vector-ref that checks for out-of-bounds references and replaces in-bounds references with unsafe accesses.
(define-tailoring (-vector-ref[e1~>e1+(φ1[vector-domain v])]
[e2~>e2+(φ2[integer-domain i])])
#:with+vector-ref(τλ #' τ-vector-ref#' λ-vector-ref)
#:with+unsafe-vector-ref(τλ #' τ-unsafe-vector-ref#' λ-unsafe-vector-ref)
(+vector-refe1+e2+)
#:+(and (<= 0i)(< in))
(+unsafe-vector-refe1+e2+)
#:-#t
(format-bounds-error#' e1+i)

Example: a map that statically checks arity and propagates the length of the result.
(define-tailoring (-map[f~>f+(φ1[A-doma])]
[e*~>e+*(φ*[L-doml*])]... )
#:with+map(τλ #' τ-map#' λ-map)
(define expected-arity(length l*))
(define arity-ok?(or (⊥? A-doma)(= (length a)expected-arity)))
(define n*(map L->Il*))
#:=(and (⊥? I-dom(⊓* I-domn*))arity-ok?)
(+mapf+e+*... )
#:+arity-ok?
(+mapf+e+*... )
#:-#t
(format-arity-error#' f+expected-arity)
#:φ(φ-set (φ-init )L-dom(I->L(⊓* I-domn*))))

3.1Property DomainsπŸ”— i

Every tailoring extracts and asserts properties about program expressions. For example, tailorings in trivial/vector assert properties about the size and contents of vector values. Tailorings in trivial/format assert properties about the formatting escapes in string values. The "properties of vectors" and "properties of format strings" are two examples of property domains.

A property domain is a lattice. In particular, each property domain D has:
  • A distinguished bottom element ( D).

    Interpretation: unknown property.

  • A (family of) top element(s) ( Dmessage).

    Interpretation: contradictory property, because message.

  • A family of elements.

  • A partial order <=? relating elements to one another, and to the top and bottom element. For any element e and any string message, both (<=?( D)e) and (<=?e( Dmessage)) hold.

syntax

( make-property-domain sym#:leqorderclause...)

sym : symbol?
order : (-> any/c any/c any/c )
Builds a new property domain from a symbol id , ordering relation order, and list of syntax-parse clauses. Specifically:
  • generates symbols to represent the top and bottom elements,

  • lifts order to account for these top and bottom elements,

  • creates a syntax-parser from clause... ,

  • registers the new syntax parser with trivial/define.

The default order does not relate any elements to one another. It is (λ (xy)#false).

Examples:
[i:integer(syntax-e #'i)]))
> (define flat-integer-domain
[i:integer(syntax-e #'i)]))

procedure

( property-domain? v)boolean?

v:any/c
Predicate for values returned by make-property-domain .

Predicate for values in a domain.

procedure

( D)(in-domain? D)

Return the bottom element of D.

procedure

( Dstr)(in-domain? D)

str:string?
Return a top element of D, with the error message str. If a tailoring encounters this element, it uses str to explain an error to the programmer.

Examples:
> (define bad-rx"foo ( bar")
> (regexp-match bad-rx"any string")

~>: [1:0] Invalid regexp pattern (unmatched '(' at index 4)

in "foo ( bar"

procedure

( ⊥? Dv)boolean?

v:any/c

procedure

( ⊤? Dv)boolean?

v:any/c
Predicates for the bottom and top elements of the given domain.

procedure

( glb Dv...)(in-domain? D)

v:(in-domain? D)

procedure

( Dv...)(in-domain? D)

v:(in-domain? D)

procedure

( glb* Dlst)(in-domain? D)

lst:(listof (in-domain? D))

procedure

( ⊓* Dlst)(in-domain? D)

lst:(listof (in-domain? D))
Greatest-lower-bound on the domain D. Assumes that none of its arguments are top elements.

Examples:

procedure

( lub Dv...)(in-domain? D)

v:(in-domain? D)

procedure

( Dv...)(in-domain? D)

v:(in-domain? D)

procedure

( lub* Dlst)(in-domain? D)

lst:(listof (in-domain? D))

procedure

( ⊔* Dlst)(in-domain? D)

lst:(listof (in-domain? D))
Least-upper-bound on D. Assumes that none of its arguments are top elements.

Examples:

procedure

( reduce Dfinitv...)(in-domain? D)

init:any/c
v:(in-domain? D)

procedure

( reduce* Dfinitlst)(in-domain? D)

init:any/c
lst:(listof (in-domain? D))
Combine the elements v... (or list of elements lst) using the binary function f. reduce lifts the given function f to handle top and bottom elements in D.

Examples:
> (reduce integer-domain + 8675309)

38

> (reduce flat-integer-domain+ 022)

4

3.1.1Built-in Property DomainsπŸ”— i

The following property domains are provided by the trivial/tailoring module.

Flat lattice of formal parameters to a function.

For example, the arity of:

(λ (xyz)x)

is:

(xyz)

Similarly, the arity of:

(λ ([a:String ][b:Integer ])42)

is:

([a:String ][b:Integer ])

Flat lattice of sequences of Typed Racket types; in particular, an element of format-string-domain satisfies the contract:
These represent the values that a format string expects, given its formatting escapes.

Totally-ordered lattice of integers, using <= .

Pointwise-ordered lattice of lists of tailoring properties.

Flat lattice of lists of booleans. The boolean value at position i determines whether the i-th group in the regular expression is guaranteed to capture a string. For example, the domain element:

(list #true#false)

describes regular expressions that capture two groups; the second group may fail to capture. One regular expression that is described by this domain element is:

#rx"(a*)(b)*"

Pointwise-ordered lattice of lists of tailoring properties.

The list-domain and vector-domain have additional operations. These are -propagating versions of their racket/list and racket/vector equivalents. (And there is probably a better or more-general way to lift these computations.)

Defines an isomorphism between the list and vector domains.

"Forgetful functors" that abstract a sequence with its length.

"Right adjoints" to the above; these convert an integer to a sequence of empty tailoring property maps.

procedure

( list-domain-cons proplst)(in-domain? list-domain )

prop:φ?

procedure

( list-domain-car lst)φ?

procedure

( list-domain-set lstposprop)(in-domain? list-domain )

prop:φ?
Extra functions on list domain elements.

Extra functions on vector domain elements.

3.2Tailoring PropertiesπŸ”— i

Tailorings associate static properties to expressions using finite maps φ . Each map relates property domains to values in the domain (or the bottom element of the domain).

procedure

( φ stx)φ?

stx:syntax?
Get the tailoring properties associated with the syntax object stx.

procedure

( φ? v)boolean?

v:any/c
Predicate for tailoring property maps.

procedure

( φ-init )φ?

Return an empty map.

procedure

( φ-ref propD)(in-domain? D)

prop:φ?
Return the value associated with domain D in the map prop.

procedure

( φ-set propDv)φ?

prop:φ?
v:(in-domain? d)
Return a map like prop, but with v associated with the domain D.

procedure

( φ<=? p1p2)boolean?

p1:φ?
p2:φ?
Pointwise ordering on tailoring property maps. Returns true when p2 has bindings for every domain D bound in p1, and furthermore the value v1 associated with D in p1 is less than the value v2 associated with D in p2 (using the order from D).

3.3MiscellaneousπŸ”— i

syntax

( τλ typed-iduntyped-id)

Chooses typed-id or untyped-id depending on whether the currently-expanding module is from typed/racket or not.

Logs events to the "ttt" topic.

top
← prev up next →

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /