Sugar
(require sugar) | package: sugar |
(require (submod sugar safe)) | |
(require typed/sugar) |
A collection of small functions to help make Racket code simpler & more readable.
Sugar can be invoked three ways: as an untyped library, as an untyped library with contracts (using the safe submodule), or as a typed library.
A few functions are only available as untyped or typed. These exceptions are noted below.
The typed version of Sugar is implemented “natively” in the sense that it is compiled separately with type annotations. It is not a require/typed wrapper around the untyped code. This avoids the contract barrier that is otherwise automatically imposed between typed and untyped code.
I explain more about this cross-compiling technique in Making a dual typed / untyped Racket library.
1 Installation & updates
raco pkg install sugar |
raco pkg update sugar |
2 Cache
(require sugar/cache) | package: sugar |
(require (submod sugar/cache safe)) | |
(require typed/sugar/cache) |
If, like Ricky Bobby and me, you want to go fast, then try using more caches. They’re wicked fast.
procedure
(make-caching-proc proc) → procedure?
proc : procedure?
In the example below, notice that both invocations of slow-op take approximately the same time, whereas the second invocation of fast-op gets its value from the cache, and is thus nearly instantaneous.
Examples: | ||||||||||||||||||
|
Keep in mind that the cache is only available to external callers of the resulting function. So if proc calls itself recursively, these calls are not accelerated by the cache. If that’s the behavior you need, use define/caching to create a new recursive function.
syntax
(define/caching (name arg ... . rest-arg) body ...)
In the example below, fib is a recursive function. Notice that simply wrapping the function in make-caching-proc doesn’t work in this case, because fib’s recursive calls to itself bypass the cache. But fib-fast is rewritten to recur on the caching function, and the caching works as expected.
Examples: | ||||||||||||||||||||||
|
3 Coercion
(require sugar/coerce) | package: sugar |
(require (submod sugar/coerce safe)) | |
(require typed/sugar/coerce) |
Functions that coerce the datatype of a value to another type. Racket already has type-specific conversion functions. But if you’re handling values of indeterminate type — as sometimes happens in an untyped language — then handling the possible cases individually gets to be a drag.
3.1 Values
Numbers are rounded down to the nearest integer.
Examples: | ||||||||
|
Stringlike values — paths, symbols, and strings — are converted to numbers and rounded down.
Examples: | ||||||
|
Characters are directly converted to integers.
Examples: | ||||
|
Lists, vectors, and other multi-value datatypes return their length (using len).
Examples: | ||||
|
Example: | ||
|
Examples: | ||||||||||||
|
Examples: | ||||||||||||
|
Examples: | ||||||||||||
|
Note that a string is treated as an atomic value rather than decomposed with string->list. This is done so the function handles strings the same way as symbols and paths.
Examples: | ||||||||||||||
|
Examples: | ||||||||||||||
|
Examples: | ||||||||||
|
procedure
v : any/c
procedure
(stringish? v) → boolean?
v : any/c
procedure
(symbolish? v) → boolean?
v : any/c
procedure
v : any/c
procedure
(complete-pathish? v) → boolean?
v : any/c
procedure
v : any/c
procedure
(vectorish? v) → boolean?
v : any/c
Examples: | ||||||||||||||
|
3.2 Coercion contracts
procedure
(coerce/int? v) → integer?
v : any/c
procedure
(coerce/string? v) → string?
v : any/c
procedure
(coerce/symbol? v) → symbol?
v : any/c
procedure
(coerce/path? v) → path?
v : any/c
procedure
(coerce/boolean? v) → boolean?
v : any/c
procedure
(coerce/list? v) → list?
v : any/c
Examples: | ||||||||||||||||||
|
Please note: this is not an officially sanctioned way to use Racket’s contract system, because contracts aren’t supposed to mutate their values (see make-contract).
But coercion contracts can be useful in two situations:
You want to be liberal about input types, but don’t want to deal with the housekeeping and manual conversions between types.
Your contract involves an expensive operation that you’d rather avoid performing twice.
4 Container
(require sugar/container) | package: sugar |
(require (submod sugar/container safe)) |
Type-neutral functions for getting elements out of a container, or testing membership. This submodule is untyped only.
procedure
container : (or/c list? vector? sequence? dict? string? symbol? path?) which : any/c end_which : (or/c (and/c integer? positive?) #f) = #f
Examples: | |||||
|
For other container types — which are all sequence-like — retrieve the element located at which. Or if the optional end_which argument is provided, retrieve the elements from which to (sub1 end_which), inclusive (i.e., make a slice). Raise an error if which or end_which is out of bounds.
Examples: | |||||||||||||||||||||
|
When container is a path, it’s treated as a list of path elements (created by explode-path), not as a stringlike value.
Examples: | ||||
|
To slice to the end of container, use (len container) as the value of end_which.
Examples: | ||||||||||||
|
Examples: | ||||||||||||||
|
As with get, when container is a path, it’s treated as a list of exploded path elements, not as a stringlike value.
Examples: | ||||
|
5 Debug
(require sugar/debug) | package: sugar |
(require (submod sugar/debug safe)) | |
(require typed/sugar/debug) |
Debugging utilities.
For instance, suppose you wanted to see how first-condition? was being evaluted in this expression:
(if (and (first-condition? x) (second-condition? x)) (one-thing) (other-thing))
You can wrap it in report and find out:
(if (and (report (first-condition? x)) (second-condition? x)) (one-thing) (other-thing))
This code will run the same way as before. But when it reaches first-condition?, you willl see in current-error-port:
(first-condition? x) = #t
You can also add standalone calls to report as a debugging aid at points where the return value will be irrelevant, for instance:
(report x x-before-function) (if (and (report (first-condition? x)) (second-condition? x)) (one-thing) (other-thing))
x-before-function = 42
(first-condition? x) = #t
But be careful — in the example below, the result of the if expression will be skipped in favor of the last expression, which will be the value of x:
(if (and (report (first-condition? x)) (second-condition? x)) (one-thing) (other-thing)) (report x)
syntax
(report/line expr)
(report/line expr maybe-name)
syntax
(report/file expr)
(report/file expr maybe-name)
syntax
(report* expr ...)
syntax
(report*/line expr ...)
syntax
(report*/file expr ...)
syntax
(repeat num expr ...)
Example: | ||||
|
syntax
(time-repeat num expr ...)
Example: | ||||||
|
syntax
(time-repeat* num expr ...)
Example: | ||||||||||
|
syntax
(compare expr id id-alt ...)
Examples: | |||||||||||||||||
|
6 File
(require sugar/file) | package: sugar |
(require (submod sugar/file safe)) | |
(require typed/sugar/file) |
File utilities, mostly in the realm of file extensions. These functions don’t access the filesystem.
Arguments that are pathish? can take either a string or a path. For clarity below, I’ve used strings.
Examples: | ||||||||||
|
procedure
file-path : pathish? ext : stringish?
Examples: | ||||||
|
procedure
(remove-ext file-path) → path?
file-path : pathish?
Examples: | ||||||||
|
procedure
(remove-ext* file-path) → path?
file-path : pathish?
Examples: | ||||||||
|
procedure
file-path : pathish? ext : stringish?
Examples: | ||||||
|
procedure
(get-enclosing-dir path) → path?
path : pathish?
Examples: | |||||||||||
|
7 Include
(require sugar/include) | package: sugar |
syntax
(include-without-lang-line path-spec)
Why? So you can take the code from a working source file and recompile it under a different #lang. Why? Well, you could take code from a #lang typed/racket source file and recompile as #lang typed/racket/no-check. Why? Because then you could make typed and untyped modules from the same code without the mandatory contracts imposed by require/typed.
8 Len
(require sugar/len) | package: sugar |
(require (submod sugar/len safe)) | |
(require typed/sugar/len) |
Examples: | ||||||||||||
|
Perhaps ironically, positive integers do not have a length.
Example: | ||
|
9 List
(require sugar/list) | package: sugar |
(require (submod sugar/list safe)) | |
(require typed/sugar/list) |
procedure
lst : list? pred : procedure?
Examples: | ||||||||
|
procedure
(filter-split lst pred) → (listof list?)
lst : list? pred : procedure?
Examples: | ||||||||
|
Examples: | ||||||||||||||||
|
procedure
lst : list? pred : procedure?
Examples: | ||||||
|
procedure
lst : list? pred : procedure? force? : boolean? = #f
Examples: | ||||||||||
|
procedure
(slicef-after lst pred) → (listof list?)
lst : list? pred : procedure?
Examples: | ||||||
|
procedure
(frequency-hash lst) → hash?
lst : list?
Examples: | ||||
|
Examples: | ||||
|
Examples: | ||||||
|
syntax
(when/splice test expr)
Examples: | ||||||||
|
syntax
(values->list values)
Examples: | ||||||
|
procedure
lst : list? start-idx : (and/c integer? (not/c negative?)) end-idx : (and/c integer? (not/c negative?))
Bear in mind that sublist is built for convenience, not performance. If you need to do a lot of random access into the middle of an ordered sequence of items, you’d be better off putting them into a vector and using vector-copy.
Examples: | ||||||||||
|
Examples: | |||||||||||
|
Examples: | |||||||||||||||||
|
Examples: | |||||||||||
|
procedure
(shift/values lst how-far [fill-item]) → any
lst : list? how-far : (or/c integer? (listof integer?)) fill-item : any/c = #f
Examples: | |||||||||||||||||||
|
10 String
(require sugar/string) | package: sugar |
(require (submod sugar/string safe)) | |
(require typed/sugar/string) |
procedure
(starts-with? str starter) → boolean?
str : stringish? starter : stringish?
Examples: | ||||||||
|
procedure
(ends-with? str ender) → boolean?
str : stringish? ender : stringish?
Examples: | ||||||||
|
procedure
(capitalized? str) → boolean?
str : stringish?
Examples: | ||||||
|
11 XML
(require sugar/xml) | package: sugar |
(require (submod sugar/xml safe)) |
Making it easier to do the simplest kind of round-trip with XML: convert an XML string to X-expressions, manipulate, and then convert these X-expressions back to an XML string. This submodule is untyped only.
procedure
(xml-string->xexprs xml-string) →
xexpr? xexpr? xml-string : string?
Examples: | |||||||||||||||||||||||
|
procedure
(xexprs->xml-string prolog-xexpr root-xexpr) → string? prolog-xexpr : xexpr? root-xexpr : xexpr?
Examples: | ||||||||||||||||
|
12 License & source code
This module is licensed under the LGPL.
Source repository at http://github.com/mbutterick/sugar. Suggestions & corrections welcome.