scramble: Assorted Utility Libraries
1 About Descriptions
(require scramble/about) | package: scramble-lib |
The about interface is useful for displaying a partial description of a value in a situation where simply printing or writing the value would be inappropriate. See also about<%>.
> (struct secret (bs) #:property prop:about (lambda (self) (format "~s-bit secret" (* 8 (bytes-length (secret-bs self)))))) > (define my-secret (secret #"hello")) > (error 'secure-op "secret is too short\n given: ~a" (about my-secret)) secure-op: secret is too short
given: 40-bit secret
> (about 'apple) "(no description)"
value
procedure
(has-about? v) → boolean?
v : any/c
2 Conditional Expressions
(require scramble/cond) | package: scramble-lib |
syntax
(cond+ clause ... maybe-final-clause)
clause = [test-expr then-body ...+] | [test-expr => proc-expr] | #:do defn-or-expr maybe-final-clause =
| [else then-body ...+] | #:else then-body ...+
#:do defn-or-expr If the preceding clauses were not selected, evaluate defn-or-expr before continuing. If defn-or-expr is a definition, the subsequent clauses are in its scope.
Separate #:do clauses have “let*” scoping. That is, each #:do clause’s form is evaluated in a separate internal definition context nested within the previous scopes. Use begin to group mutually recursive definitions.
#:else then-body ...+ Allowed only at the end. Equivalent to [else then-body ...].
> (define entries `([x 5] [y ,(lambda (key) (list key 12))]))
> (define (lookup key) (define entry (assoc key entries)) (cond+ [(not entry) (error "not found")] #:do (define v (cadr entry)) [(procedure? v) (v key)] #:else v)) > (lookup 'x) 5
> (lookup 'y) '(y 12)
> (lookup 'z) not found
> (cond+ [(odd? 12) 'odd] [(even? 7) 'even-odder]) cond+: all clauses failed
location: eval:7:0
syntax
(and+ part ... expr)
part = expr | #:do defn-or-expr
syntax
(or+ part ... expr)
part = expr | #:do defn-or-expr
3 Synchronizable Events
(require scramble/evt) | package: scramble-lib |
procedure
(make-box-evt) → box-evt?
Box events are thread-safe and break-safe but not kill-safe: If a thread is
killed during a call to box-evt-set!, it is possible for the box event
to become damaged—
> (define bxe (make-box-evt)) > (sync/timeout 0 bxe) #f
> (box-evt-set! bxe (list 1 2 3)) #t
> (sync/timeout 0 bxe) '(1 2 3)
> (box-evt-set! bxe (list 7 8 9)) #f
> (sync/timeout 0 bxe) '(1 2 3)
procedure
(box-evt-set! bxe v) → boolean?
bxe : box-evt? v : any/c
procedure
(box-evt-ready? bxe) → boolean?
bxe : box-evt?
Equivalent to (sync/timeout 0 (wrap-evt bxe (lambda (v) #t))).
4 Functions
(require scramble/function) | package: scramble-lib |
procedure
(K v ...) → procedure?
v : any/c
The resulting constant function’s arity is (arity-at-least 0); that is, it accepts (and discards) any number of positional arguments, but it does not accept keyword arguments. See const for a single-valued version whose result accepts keyword arguments.
> (define always-zero (K 0)) > (map always-zero '(1 2 3)) '(0 0 0)
> (define two-falses (K #f #f))
> (define-values (head tail) (with-handlers ([exn? two-falses]) (values (car '()) (cdr '()))))
procedure
(K0 v ...) → procedure?
v : any/c
procedure
f : procedure? v : any/c
> (define the-callbacks (list (lambda () (printf "here I am\n")) (lambda () (printf "me too!\n")))) > (map call the-callbacks)
here I am
me too!
'(#<void> #<void>)
5 Immutable and Mutable Conversion
(require scramble/immutable) | package: scramble-lib |
The result is immutable, and it is not an impersonator. If v is a suitable result, it is returned as the result; that is, the result of the function may not be a fresh value.
Note that if v is impersonated, this function can raise an exception due to accessing v.
If fresh? is true, then the result is a new value that does not share storage (shallowly) with v.
Note that if v is impersonated, this function can raise an exception due to accessing v.
6 Lists
(require scramble/list) | package: scramble-lib |
procedure
(singleton? v) → boolean?
v : any/c
Equivalent to (and (pair? v) (null? (cdr v))).
> (singleton? (list 'hello)) #t
> (singleton? (list 1 2 3)) #f
> (singleton? (shared ([whys (cons 'why whys)]) whys)) #f
7 Numbers
(require scramble/number) | package: scramble-lib |
procedure
(ceiling-quotient n d) → exact-integer?
n : exact-integer? d : exact-positive-integer?
> (ceiling-quotient 7 4) 2
> (ceiling-quotient 8 4) 2
> (ceiling-quotient 9 4) 3
procedure
(ceiling-multiple n d) → exact-integer?
n : exact-integer? d : exact-positive-integer?
Equivalent to (* d (ceiling-quotient n d)).
> (ceiling-multiple 7 4) 8
> (ceiling-multiple 8 4) 8
> (ceiling-multiple 9 4) 12
procedure
(floor-quotient n d) → exact-integer?
n : exact-integer? d : exact-positive-integer?
procedure
(floor-multiple n d) → exact-integer?
n : exact-integer? d : exact-positive-integer?
8 Regular Expressions
(require scramble/regexp) | package: scramble-lib |
Added in version 0.3 of package scramble-lib.
This module works with the following S-expression representation of regular expressions. All literals in the grammar are recognized as symbols, not by binding.
RE | = | RE-id | ||
| | (or RE ...+) ; like <RE>|<RE> | |||
| | (cat RE ...) ; like <RE><RE> | |||
| | (repeat RE) ; like <RE>* | |||
| | (repeat RE n) ; like <RE>{n} | |||
| | (repeat RE m n) ; like <RE>{m,n} | |||
| | (* RE) ; like <RE>* | |||
| | (+ RE) ; like <RE>+ | |||
| | (? RE) ; like <RE>? | |||
| | (report RE) ; like (<RE>) | |||
| | ^ ; like ^ | |||
| | $ ; like $ | |||
| | (mode modes-string RE) ; like (?<modes>:<RE>) | |||
| | (test tst RE) ; like (?<tst><RE>) | |||
| | (test tst RE RE) ; like (?<tst><RE>|<RE>) | |||
| | (unicode prop-string) ; like \p{<prop>} | |||
| | (unicode (not prop-string)) ; like \P{<prop>} | |||
| | (chars CharSet ...) ; like [<CharSet>] | |||
| | Look | |||
| | literal-string | |||
| | (inject pregexp-string) | |||
CharSet | = | (union CharSet ...) | ||
| | (intersect CharSet ...) | |||
| | (complement CharSet ...) | |||
| | chars-string | |||
| | CharRange ; eg, [#\A #\Z] | |||
| | char/integer ; eg, #\A, 65 | |||
| | RE-id ; if value is CharSet | |||
| | posix-charset-id ; eg, alpha, space | |||
CharRange | = | [lo:char/integer hi:char/integer] | ||
Test | = | Look | ||
| | (matched? n) | |||
Look | = | (look RE) ; like (?=<RE>) | ||
| | (look (not RE)) ; like (?!<RE>) | |||
| | (look-back RE) ; like (?<=<RE>) | |||
| | (look-back (not RE)) ; like (?<!<RE>) |
The forms of RE should mostly be self-explanatory, but a few of them deserve additional comments:
RE-id If RE-id was defined using define-RE, then its RE value is inserted in place of RE-id; otherwise, a syntax error is raised.
If an RE-id is defined with the same name as one of the unparenthesized RE forms (namely, ^ or $) or one of the POSIX character classes (eg, alpha), then the RE-id takes precedence.
(repeat RE m n) Matches RE between m and n times (inclusive), where m must be a natural number and n must be a natural number or +inf.0.
(repeat RE n) is equivalent to (repeat RE n n)
(* RE) and (repeat RE) are both equivalent to (repeat RE 0 +inf.0)
(+ RE) is equivalent to (repeat RE 1 +inf.0)
(? RE) is equivalent to (repeat RE 0 1)
(chars CharSet ...) Interprets (union CharSet ...) as a set of characters. The resulting set of characters must be non-empty; otherwise, a syntax error is raised. Generation of the pregexp literal depends on only the set of characters, not how it was originally expressed.
chars-string Represents the set of characters appearing in the string. No character in the string is interpreted specially. For example, - represents the character #\-; it is not interpreted as a range.
Note that a RE literal-string is treated differently.
literal-string A string RE is treated as the concatenation (cat) of singleton character sets that matches exactly that string. Special characters in the string are escaped when the pregexp is generated. For example:
> (px "[ab]*z?") #px"\\[ab\\]\\*z\\?"
> (regexp-match-exact? (px "[ab]*z?") "[ab]*z?") #t
Note that a CharSet chars-string is treated differently.
(inject pregexp-string) Injects the given pregexp-string into the generated output. It is treated as having lowest precedence, so it will be wrapped if it occurs within a higher-precedence operator. For example:
> (px (* (inject "[ab]"))) #px"(?:[ab])*"
syntax
(px part-RE ...)
> (px (cat "A" (or "BB" "CCC"))) #px"A(?:BB|CCC)"
syntax
(rx part-RE ...)
> (rx (cat "A" (or "BB" "CCC"))) #rx"A(?:BB|CCC)"
> (rx (repeat (or "a" "b") 2 5)) #rx"[ab][ab](?:(?:(?:[ab])?[ab])?[ab])?"
> (rx (repeat (report "a") 2 5)) eval:8:0: rx: cannot handle report inside of repeat with
custom bounds
in: (rx (repeat (report "a") 2 5))
> (rx (repeat (report "a") 1 +inf.0)) #rx"(a)+"
> (rx (+ (chars alpha digit))) #rx"[0-9A-Za-z]+"
syntax
(define-RE name rhs-RE)
If name is used as an expression, it expands to rhs-RE’s corresponding pregexp literal.
> (define-RE As (* "A")) > (define-RE BBs (* "BB")) > BBs #px"(?:BB)*"
> (px (or As BBs)) #px"A*|(?:BB)*"
9 Results
(require scramble/result) | package: scramble-lib |
Added in version 0.3 of package scramble-lib.
This module defines a result type. In general, a result is either ok and carries a success value, or it is bad and carries a value representing failure.
> (ok 5) ; (Result Integer) '#s(ok 5)
> (ok (ok 5)) ; (Result (Result Integer)) '#s(ok #s(ok 5))
> (ok 'hello) ; (Result Symbol) '#s(ok hello)
> (ok (list 1 2 3)) ; (Result (Listof Integer)) '#s(ok (1 2 3))
> (ok (bad 123)) ; (Result (Result _ Integer)) '#s(ok #s(bad 123))
procedure
(partition-results rs) →
(listof X) (listof Y) rs : (listof (result/c X Y))
> (partition-results (list (ok 1) (bad 2) (ok 3)))
'(1 3)
'(2)
10 Slices
(require scramble/slice) | package: scramble-lib |
struct
value : (or/c bytes? string? vector? slice?) start : exact-nonnegative-integer? end : exact-nonnegative-integer?
The start and end fields must be appropriate for value, and start must be less than or equal to end; otherwise, an exception is raised.
If end is #f, it is replaced with the length of the given value. That is slice-end never returns #f.
If value is a slice, then its value is extracted and start and end are adjusted to refer to the underlying value. That is, slice-value never returns a slice.
If start is equal to end, then value is replaced with the empty value of the same type, and start and end are set to 0.
See print-slice-constructor-modes for information about printing slices.
Note: Future versions of this library may extend the set of types allowed as values.
procedure
(bytes-slice? v) → boolean?
v : any/c
procedure
(string-slice? v) → boolean?
v : any/c
procedure
(vector-slice? v) → boolean?
v : any/c
parameter
(print-slice-constructor-modes) → (listof (or/c #t #f 0 1))
(print-slice-constructor-modes modes) → void? modes : (listof (or/c #t #f 0 1))
= '(0 1)
When a slices is printed using a mode (see gen:custom-write) in modes, it is printed as a struct; otherwise, only its contents are printed.
> (define s (slice (for/vector ([i 16]) i) 9 13)) > (print s) (slice '#(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) 9 13)
> (write s) #(9 10 11 12)
> (parameterize ((print-slice-constructor-modes '(#t))) (write s)) #<slice: #(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) 9 13>
procedure
s : slice?
Equivalent to (- (slice-end s) (slice-start s)).
procedure
(slice-contents s mutability) → (or/c bytes? string? vector?)
s : slice? mutability : (or/c 'mutable 'immutable #f)
If mutability is 'mutable, the result is mutable; if 'immutable, the result is immutable; otherwise, the result is immutable when the slice’s underlying value is immutable.
procedure
(bytes-slice->string/utf-8 bs [err-char]) → string?
bs : bytes-slice? err-char : (or/c char? #f) = #f
procedure
(string-slice->bytes/utf-8 ss) → bytes?
ss : string-slice?
> (bytes-slice->string/utf-8 (slice #"hello world" 6 #f)) "world"
11 Structs
(require scramble/struct) | package: scramble-lib |
value
:
(struct-type-property/c (or/c #t (listof exact-nonnegative-integer?)))
In addition to the indicated fields, the hash code function also depends on a random seed and the name of the struct type. A new random seed is generated for each instantiation of the scramble/struct module.
If prop:auto-equal+hash is attached to a struct type that has a super struct type, then the super struct type must also have the prop:auto-equal+hash, and the new equality and hash code functions extend the super type’s functions. If the super struct type does not have the prop:auto-equal+hash property, an error is raised.
> (struct point (x y) #:property prop:auto-equal+hash #t) > (equal-hash-code (point 1 2)) 1131436441982744649
> (equal? (point 1 2) (point 1 2)) #t
> (equal? (point 1 2) (point 0 0)) #f
In the following example, the equality and hash code functions of the point3 struct type use only the z field out of point3’s fields, disregarding the color field, but they also use both of point’s fields.
> (struct point3 point (z color) #:property prop:auto-equal+hash (list (struct-field-index z)))
> (equal? (point3 1 2 3 #f) (point3 1 2 3 'red)) #t
> (equal? (point3 0 0 3 'red) (point3 1 2 3 'red)) #f
> (equal? (equal-hash-code (point3 1 2 3 #f)) (equal-hash-code (point3 1 2 3 'red))) #t
12 Classes and Objects
(require scramble/class) | package: scramble-lib |
syntax
(init-private init-decl ...)
> (define person% (class object% (init-field name) (init-private [id #f]) (super-new))) > (define alice (new person% (name "Alice") (id 1001))) > (get-field name alice) "Alice"
> (get-field id alice) get-field: given object does not have the requested field
field name: id
object: (object:person% ...)
|
method
(send a-constructor-style-printable get-printing-class-name)
→ symbol? Returns a symbol to be used as the class name when printed.
method
(send a-constructor-style-printable get-printing-components)
→
(listof symbol?) (listof any/c) boolean? Returns (values names values more?), which control the printing of the object’s contents as follows:The names represent the names of the object’s fields—
or more properly, its init arguments. The values are the corresponding values; the two lists should have the same length. If more? is true, then ... is printed after the initialization arguments to indicate that the object contains additional state not represented by the printed output.
Examples:
> (define friendly-person% (class* person% (constructor-style-printable<%>) (inherit-field name) (super-new) (define/public (get-printing-class-name) 'friendly-person%) (define/public (get-printing-components) (values '(name) (list name) #t)))) > (define bob (new friendly-person% (name "Bob"))) > bob (new friendly-person% (name "Bob") ...)
> (list bob) '(#<friendly-person%: (name "Bob") ...>)
> (write bob) #<friendly-person%: (name "Bob") ...>
|
> (define expressive-person% (class* friendly-person% (print-quotable-never<%>) (super-new) (define/override (get-printing-class-name) 'expressive-person%))) > (define kristen (new expressive-person% (name "Kristen"))) > kristen (new expressive-person% (name "Kristen") ...)
> (list kristen) (list (new expressive-person% (name "Kristen") ...))
> (write kristen) #<expressive-person%: (name "Kristen") ...>
|
> (define obscure-person% (class* friendly-person% (print-quotable-always<%>) (super-new) (define/override (get-printing-class-name) 'obscure-person%))) > (define jeff (new obscure-person% (name "Jeff"))) > jeff '#<obscure-person%: (name "Jeff") ...>
> (list jeff) '(#<obscure-person%: (name "Jeff") ...>)
> (write jeff) #<obscure-person%: (name "Jeff") ...>
|
13 Compile-time Code Injection
(require scramble/inject-syntax) | package: scramble-lib |
syntax
(begin/inject-syntax body ...+)
Any side-effects performed by the bodys occur only once, when the begin/inject-syntax form is compiled. This is in contrast to begin-for-syntax, for example, whose contents are also evaluated whenever the enclosing module is visited.
If begin/inject-syntax is used in an expression context, the resulting syntax object must be an expression form; otherwise, the macro expander will raise a syntax error.
> (require (for-syntax racket/base) scramble/inject-syntax)
> (begin-for-syntax (define (version-less-than? v) .... (version) ....))
> (begin/inject-syntax (if (version-less-than? "7.6") ; racket/symbol was added in 7.6 #'(begin (define (symbol->immutable-string s) (string->immutable-string (symbol->string s)))) #'(begin (require (only-in racket/symbol symbol->immutable-string))))) > (symbol->immutable-string 'hello) "hello"
> (require racket/string)
> (begin/inject-syntax (if (identifier-binding #'string-exclaim) ; Already defined? (No) #'(begin) #'(define (string-exclaim str) (regexp-replace* #rx"[.]" str "!")))) > (string-exclaim "Thanks. Have a nice day.") "Thanks! Have a nice day!"
> (require racket/require) > (define-for-syntax use-safe-fx-ops? #t)
> (begin/inject-syntax (if use-safe-fx-ops? #'(require (prefix-in unsafe- racket/fixnum)) #'(require (matching-identifiers-in #rx"^unsafe-fx" racket/unsafe/ops)))) > (unsafe-fx+ 1 2) 3
Keep in mind that it is customary in Racket to do library configuration at run time via ordinary variables, parameters, etc. Prefer run-time mechanisms when possible. Use begin/inject-syntax when compile-time concerns are involved, such as scoping and variations in module exports.
> (begin-for-syntax (define (slow-popcount n) (cond [(zero? n) 0] [else (+ (slow-popcount (quotient n 2)) (if (bitwise-bit-set? n 0) 1 0))])))
> (define (faster-popcount n) (define (byte-popcount b) (bytes-ref (begin/inject-syntax #`(quote #,(apply bytes (for/list ([n 256]) (slow-popcount n))))) b)) (cond [(zero? n) 0] [else (+ (faster-popcount (arithmetic-shift n -8)) (byte-popcount (bitwise-bit-field n 0 8)))])) > (faster-popcount #xFFFFFFFF) 32
Warning: Code can be run on a different platform from the one it was compiled on. Don’t use compile-time conditions to specialize code based on features that may change between compile time and run time. On Racket BC, these include the size of fixnums, the operating system, path conventions, and so on. On Racket CS, these features might not change, but beware of cross-compilation.
14 Compile-time Struct Info
(require scramble/struct-info) | package: scramble-lib |
procedure
(adjust-struct-info base-info [ #:constructor constructor-id #:match-expander match-expander]) → struct-info? base-info : struct-info? constructor-id : (or/c identifier? #f) = #f match-expander : (or/c #f (-> syntax? syntax?)) = #f
If base-info is an instance of a struct type with any of the following properties, the result has the same properties with the same values: prop:struct-auto-info, prop:struct-field-info. Other properties of base-info are not preserved.
> (module point racket/base (provide (struct-out point)) (struct point (x y) #:transparent))
> (module point-adjusted racket/base (require (for-syntax racket/base scramble/struct-info) racket/contract (rename-in 'point [point orig-point])) (define (make-point x [y 0]) (orig-point x y)) (define-module-boundary-contract checked-make-point make-point (->* [real?] [real?] point?) #:name-for-blame point) (define-syntax point (adjust-struct-info (syntax-local-value #'orig-point) #:constructor #'checked-make-point)) (provide (struct-out point))) > (require 'point-adjusted) > (point 1 2) (point 1 2)
> (point 3) (point 3 0)
> (point 'hello 'goodbye) point: contract violation
expected: real?
given: 'hello
in: the 1st argument of
(->* (real?) (real?) point?)
contract from: 'point-adjusted
blaming: top-level
(assuming the contract is correct)
at: eval:2:0
> (struct point3 point (z) #:transparent) > (point3 1 2 3) (point3 1 2 3)