The trivial module exports tailored versions of standard library functions.
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.
For example, make a new Racket module with a malformed call to format :
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:
(cond(get-sidekick"batmanandalfred")
It should compile and run, returning "alfred". Great. Now try converting it to Typed Racket:
(cond(get-sidekick"batmanandalfred")
The file doesn’t compile anymore. Adding (require trivial) to the top of the file removes the error.
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.
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.
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)
syntax
( curry f)
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.
procedure
( build-list kproc)→list?
procedure
( length lst)→exact-nonnegative-integer?
lst: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.
procedure
( byte-regexp byt)→byte-regexp?
byt:bytes?procedure
( byte-pregexp byt)→byte-pregexp?
byt:bytes?
procedure
( regexp-match patterninput)
This example is adapted from scribble/html-render
procedure
str:string?str:string?procedure
( string-append str...)→string?
str:string?
procedure
bstr:bytes?bstr:bytes?procedure
( bytes-append bstr...)→bytes?
bstr:bytes?
- : (U (List String String) False)
'("aaa" "aaa")
procedure
( build-vector kproc)→vector?
procedure
vec:vector?procedure
( vector->list vec)→list?
vec:vector?procedure
( vector->immutable-vector vec)→vector?
vec:vector?
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.
The bindings documented in this section are not provided by the trivial module.
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(+add1e+)#:+#t
-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.
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
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-unlessexprexprdefn = (define. expr)test-expr = exprprop-expr = exprelab-> = ~>| ⇝map-> = ->| ↦
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.
#:=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.
(+vle+)
(+vector-refe1+e2+)(+unsafe-vector-refe1+e2+)#:-#t
#:+arity-ok?#:-#t
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 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.
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).
procedure
( property-domain? v)→boolean?
v:any/c
procedure
( in-domain? D)→(-> any/c boolean? )
procedure
( ⊥ D)→(in-domain? D)
procedure
( ⊤ Dstr)→(in-domain? D)
str:string?
~>: [1:0] Invalid regexp pattern (unmatched '(' at index 4)
in "foo ( bar"
-3
#t
6
2
procedure
( reduce Dfinitv...)→(in-domain? D)
init:any/cprocedure
( reduce* Dfinitlst)→(in-domain? D)
init:any/c
38
4
The following property domains are provided by the trivial/tailoring module.
value
(λ (xyz)x)
(xyz)
(listof (or/c Char Exact-Number ))
value
value
(list #true#false)
#rx"(a*)(b)*"
value
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.)
procedure
procedure
procedure
( list-domain->integer-domain l)→(in-domain? integer-domain )
procedure
( vector-domain->integer-domain v)→(in-domain? integer-domain )
procedure
procedure
( integer-domain->vector-domain i)→(in-domain? vector-domain )
procedure
( list-domain-append* lst*)→(in-domain? list-domain )
procedure
( list-domain-cons proplst)→(in-domain? list-domain )
prop:φ?procedure
( list-domain-car lst)→φ?
procedure
( list-domain-cdr lst)→(in-domain? list-domain )
procedure
( list-domain-length lst)→(in-domain? integer-domain )
procedure
( list-domain-ref lstpos)→φ?
procedure
( list-domain-reverse lst)→(in-domain? list-domain )
procedure
( list-domain-set lstposprop)→(in-domain? list-domain )
prop:φ?procedure
( list-domain-slice lstlohi)→(in-domain? list-domain )
procedure
( vector-domain-append* lst*)→(in-domain? vector-domain )
procedure
( vector-domain-length lst)→(in-domain? integer-domain )
procedure
( vector-domain-ref lstpos)→φ?
procedure
( vector-domain-set lstposprop)→(in-domain? vector-domain )
prop:φ?procedure
( vector-domain-slice lstlohi)→(in-domain? vector-domain )
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
( φ-ref propD)→(in-domain? D)
prop:φ?
prop:φ?
syntax
( τλ typed-iduntyped-id)
value