Sugar
1 Installation & updates
2 Cache
make-caching-proc
define/  caching
3 Coercion
3.1 Values
->int
->string
->symbol
->path
->complete-path
->list
->vector
->boolean
intish?
stringish?
symbolish?
pathish?
complete-pathish?
listish?
vectorish?
3.2 Coercion contracts
coerce/  int?
coerce/  string?
coerce/  symbol?
coerce/  path?
coerce/  boolean?
coerce/  list?
4 Debug
report
report/  line
report/  file
report*
report*/  line
report*/  file
repeat
time-repeat
time-repeat*
compare
5 File extensions
get-ext
has-ext?
remove-ext
remove-ext*
add-ext
6 Lists
trimf
filter-split
partition*
slice-at
slicef
slicef-at
slicef-after
frequency-hash
members-unique?
members-unique?/  error
values->list
sublist
break-at
shift
shift-left
shift-cycle
shift-left-cycle
shifts
shift/  values
7 XML
xml-string->xexprs
xexprs->xml-string
8 License & source code
8.15.0.3

Sugar🔗ℹ

Matthew Butterick <mb@mbtype.com>

 (require sugar) package: sugar
 (require (submod sugar safe))

A collection of small functions to help make Racket code simpler & more readable. Well, according to me, anyhow.

Sugar can be invoked two ways: as an ordinary library, or as a library with contracts (using the safe submodule).

1 Installation & updates🔗ℹ

At the command line:

raco pkg install sugar

After that, you can update the package from the command line:

raco pkg update sugar

2 Cache🔗ℹ

 (require sugar/cache) package: sugar
 (require (submod sugar/cache safe))

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?
Make a caching version of proc. This means a hash table will be attached to proc, and result values will automatically be saved & retrieved. The arguments to the procedure are used as the hash key.

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:
> (define (slow-op x) (for/sum ([i (in-range 100000000)]) i))
> (time (slow-op 42))

cpu time: 252 real time: 252 gc time: 0

4999999950000000

> (time (slow-op 42))

cpu time: 268 real time: 268 gc time: 0

4999999950000000

> (define fast-op (make-caching-proc slow-op))
> (time (fast-op 42))

cpu time: 278 real time: 278 gc time: 3

4999999950000000

> (time (fast-op 42))

cpu time: 0 real time: 0 gc time: 0

4999999950000000

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 ...)

Like define, but automatically uses make-caching-proc to define a caching version of the function. If the function is recursive, the cache will be used for the recursive calls.

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:
> (define (fib x)
    (if (< x 2) 1 (+ (fib (- x 1)) (fib (- x 2)))))
> (define fibber (make-caching-proc fib))
> (define/caching (fib-fast x)
    (if (< x 2) 1 (+ (fib-fast (- x 1)) (fib-fast (- x 2)))))
> (time (fib 32))

cpu time: 72 real time: 72 gc time: 0

3524578

> (time (fibber 32))

cpu time: 86 real time: 86 gc time: 0

3524578

> (time (fib-fast 32))

cpu time: 0 real time: 0 gc time: 0

3524578

3 Coercion🔗ℹ

 (require sugar/coerce) package: sugar
 (require (submod sugar/coerce safe))

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🔗ℹ

procedure

(->int v)  integer?

  v : any/c
Convert v to an integer in the least surprising way, or raise an error if no conversion is possible.

Numbers are rounded down to the nearest integer.

Examples:
> (->int 3)

3

> (->int 3.5)

3

> (->int -2.5)

-3

> (->int (+ 3 (/ 1 2)))

3

Stringlike values — paths, symbols, and strings — are converted to numbers and rounded down.

Examples:
> (->int "3.5")

3

> (->int '3.5)

3

> (->int (string->path "3.5"))

3

Characters are directly converted to integers.

Examples:
> (->int #\A)

65

> (->int #\◊)

9674

Lists, vectors, and other multi-value datatypes return their length (using len).

Examples:
> (->int (list 5 6 7))

3

> (->int (hash 'a 1 'b 2 'c 3))

3

The function will raise an error if no sensible conversion is possible.

Example:
> (->int #t)

->int: contract violation:

  expected: intish?

  given: #t

  argument position: 1st

procedure

(->string v)  string?

  v : any/c
Return the most natural string representation of v, or raise an error if none exists.

Examples:
> (->string "string")

"string"

> (->string 'symbol)

"symbol"

> (->string 98.6)

"98.6"

> (->string (string->path "stdio.h"))

"stdio.h"

> (->string #\A)

"A"

> (->string #t)

->string: contract violation:

  expected: stringish?

  given: #t

  argument position: 1st

procedure

(->symbol v)  symbol?

  v : any/c
Same as ->string, but return a symbol rather than a string.

Examples:
> (->symbol "string")

'string

> (->symbol 'symbol)

'symbol

> (->symbol 98.6)

'|98.6|

> (->symbol (string->path "stdio.h"))

'stdio.h

> (->symbol #\A)

'A

> (->symbol #t)

->symbol: contract violation:

  expected: symbolish?

  given: #t

  argument position: 1st

procedure

(->path v)  path?

  v : any/c

procedure

(->complete-path v)  complete-path?

  v : any/c
Same as ->string, but return a path (or complete path) rather than a string.

Examples:
> (->path "string")

#<path:string>

> (->path 'symbol)

#<path:symbol>

> (->complete-path 98.6)

#<path:/home/root/user/.local/share/racket/8.15.0.3/pkgs/sugar/sugar/98.6>

> (->complete-path (string->path "stdio.h"))

#<path:/home/root/user/.local/share/racket/8.15.0.3/pkgs/sugar/sugar/stdio.h>

> (->complete-path #\A)

#<path:/home/root/user/.local/share/racket/8.15.0.3/pkgs/sugar/sugar/A>

> (->complete-path #t)

->complete-path: contract violation:

  expected: complete-pathish?

  given: #t

  argument position: 1st

procedure

(->list v)  list?

  v : any/c
If v is a listlike data type — a vector, set, stream, sequence, or list — convert it to a list. A hash or dictionary becomes a list using dict->list. If v is an atomic value, turn it into a single-member list.

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:
> (->list '(a b c))

'(a b c)

> (->list (list->vector '(a b c)))

'(a b c)

> (->list (make-hash '((k . v) (k2 . v2))))

'((k . v) (k2 . v2))

> (->list "string")

'("string")

> (->list 'symbol)

'(symbol)

> (->list (string->path "path"))

'(#<path:path>)

> (->list +)

'(#<procedure:+>)

procedure

(->vector v)  vector?

  v : any/c
Same as ->list, but returns a vector rather than a list.

Examples:
> (->vector '(a b c))

'#(a b c)

> (->vector (list->vector '(a b c)))

'#(a b c)

> (->vector (make-hash '((k . v) (k2 . v2))))

'#((k . v) (k2 . v2))

> (->vector "string")

'#("string")

> (->vector 'symbol)

'#(symbol)

> (->vector (string->path "path"))

'#(#<path:path>)

> (->vector +)

'#(#<procedure:+>)

procedure

(->boolean v)  boolean?

  v : any/c
Return #t for all v except #f, which remains #f.

Examples:
> (->boolean "string")

#t

> (->boolean 'symbol)

#t

> (->boolean +)

#t

> (->boolean '(l i s t))

#t

> (->boolean #f)

#f

procedure

(intish? v)  boolean?

  v : any/c

procedure

(stringish? v)  boolean?

  v : any/c

procedure

(symbolish? v)  boolean?

  v : any/c

procedure

(pathish? v)  boolean?

  v : any/c

procedure

(complete-pathish? v)  boolean?

  v : any/c

procedure

(listish? v)  boolean?

  v : any/c

procedure

(vectorish? v)  boolean?

  v : any/c
Predicates that report whether v can be coerced to the specified type.

Examples:
> (map intish? (list 3 3.5 #\A "A" + #t))

'(#t #t #t #t #f #f)

> (map stringish? (list 3 3.5 #\A "A" + #t))

'(#t #t #t #t #f #f)

> (map symbolish? (list 3 3.5 #\A "A" + #t))

'(#t #t #t #t #f #f)

> (map pathish? (list 3 3.5 #\A "A" + #t))

'(#t #t #t #t #f #f)

> (map complete-pathish? (list 3 3.5 #\A "A" + #t))

'(#t #t #t #t #f #f)

> (map listish? (list 3 3.5 #\A "A" + #t))

'(#t #t #t #t #t #t)

> (map vectorish? (list 3 3.5 #\A "A" + #t))

'(#t #t #t #t #t #t)

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
If v can be coerced to the specified type, change it to that type, then return it. If not, raise the usual contract error. These contracts can be used with input or output values.

Examples:
> (define/contract (add-ints x y)
      (coerce/int? coerce/int? . -> . any/c)
      (+ x y))
; Input arguments will be coerced to integers, then added
> (add-ints 1.6 3.8)

4

> (define/contract (int-sum x y)
      (any/c any/c . -> . coerce/int?)
      (+ x y))
; Input arguments will be added, and the result coerced to an integer
> (int-sum 1.6 3.8)

5

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:

4 Debug🔗ℹ

 (require sugar/debug) package: sugar
 (require (submod sugar/debug safe))

Debugging utilities.

syntax

(report expr)

(report expr maybe-name)
Print the name and value of expr to current-error-port, but also return the evaluated result of expr as usual. This lets you see the value of an expression or variable at runtime without disrupting any of the surrounding code. Optionally, you can use maybe-name to change the name shown in current-error-port.

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)
Same as report, but also shows the line number of expr.

syntax

(report/file expr)

(report/file expr maybe-name)
Same as report, but also shows the line number and source-file name of expr.

syntax

(report* expr ...)

syntax

(report*/line expr ...)

syntax

(report*/file expr ...)

Apply the relevant report macro separately to each expr in the list.

syntax

(repeat num expr ...)

Evaluate expr repeatedly — num times, in fact — and return the last value.

Example:
> (repeat 1000
  (for/sum ([i (in-range 1000)]) i))

499500

syntax

(time-repeat num expr ...)

Shorthand for using time with repeat. Repeat the whole list of expressions, print the total time, and return the last value.

Example:
> (time-repeat 1000
  (for/product ([i (in-range 1000)]) i)
  (for/sum ([i (in-range 1000)]) i))

cpu time: 5 real time: 5 gc time: 0

499500

syntax

(time-repeat* num expr ...)

Apply time-repeat to each expr individually.

Example:
> (time-repeat* 1000
  (for/product ([i (in-range 1000)]) i)
  (for/sum ([i (in-range 1000)]) i))

cpu time: 2 real time: 2 gc time: 0

cpu time: 2 real time: 2 gc time: 0

0

499500

syntax

(compare expr id id-alt ...)

Evaluate expr first using id, and then again substituting id-alt in place of id, and then again for every other id-alt in the list. This is useful for comparing the performance of multiple versions of a function.

Examples:
> (define (fib x)
    (if (< x 2) 1 (+ (fib (- x 1)) (fib (- x 2)))))
> (define/caching (fib-fast x)
    (if (< x 2) 1 (+ (fib-fast (- x 1)) (fib-fast (- x 2)))))
> (compare (time (fib 34)) fib fib-fast)

cpu time: 190 real time: 190 gc time: 0

cpu time: 0 real time: 0 gc time: 0

9227465

9227465

5 File extensions🔗ℹ

 (require sugar/file) package: sugar
 (require (submod sugar/file safe))

These functions don’t access the filesystem. Warning: these functions adopt the simplifying assumption that the paths are encoded as ASCII or UTF-8. A fully precise treatment of paths would need to handle them as byte strings. If you need that, see the functions in racket/path. This library will remain naive.

Arguments that are pathish? can take either a string or a path. For clarity below, I’ve used strings.

procedure

(get-ext file-path)  (or/c #f string?)

  file-path : pathish?
Return the last file extension of file-path as a string, or #f if it has no extension. Omit the intervening . separator.

Examples:
> (get-ext "foo.txt")

"txt"

> (get-ext "/path/to/foo.txt")

"txt"

> (get-ext "/path/to/foo.txt.bar")

"bar"

> (get-ext "/path/to/file-without-extension")

#f

> (get-ext "/path/to/directory/")

#f

procedure

(has-ext? file-path ext)  boolean?

  file-path : pathish?
  ext : stringish?
Return #t if the last file extension of file-path is ext, otherwise #f. Not sensitive to case.

Examples:
> (has-ext? "foo.txt" "txt")

#t

> (has-ext? "foo.txt" "TXT")

#t

> (has-ext? "foo.txt" "jpg")

#f

> (has-ext? "foo.jpg.txt" "jpg")

#f

procedure

(remove-ext file-path)  path?

  file-path : pathish?
Remove the last file extension of file-path, and return the path that remains. If file-path has no extension, you just get the same file-path. Does not use the filesystem.

Examples:
> (remove-ext "foo.txt")

#<path:foo>

> (remove-ext "/path/to/foo.txt")

#<path:/path/to/foo>

> (remove-ext "/path/to/foo.txt.bar")

#<path:/path/to/foo.txt>

> (remove-ext (remove-ext "/path/to/foo.txt.bar"))

#<path:/path/to/foo>

procedure

(remove-ext* file-path)  path?

  file-path : pathish?
Like remove-ext, just more. Remove all file extensions from file-path, and return the path that remains. If file-path has no extensions, you just get the same file-path. Does not use the filesystem.

Examples:
> (remove-ext* "foo.txt")

#<path:foo>

> (remove-ext* "/path/to/foo.txt")

#<path:/path/to/foo>

> (remove-ext* "/path/to/foo.txt.bar")

#<path:/path/to/foo>

> (remove-ext* (remove-ext* "/path/to/foo.txt.bar"))

#<path:/path/to/foo>

procedure

(add-ext file-path ext)  path?

  file-path : pathish?
  ext : stringish?
Return a new file-path with ext appended. Note that this does not replace an existing file extension. If that’s what you want, then do (add-ext (remove-ext file-path) ext).

Examples:
> (add-ext "foo" "txt")

#<path:foo.txt>

> (add-ext "foo.txt" "jpg")

#<path:foo.txt.jpg>

> (add-ext (remove-ext "foo.txt") "jpg")

#<path:foo.jpg>

6 Lists🔗ℹ

 (require sugar/list) package: sugar
 (require (submod sugar/list safe))

procedure

(trimf lst pred)  list?

  lst : list?
  pred : procedure?
Drop elements from each end of lst that satisfy pred. Exactly equivalent to (dropf-right (dropf lst pred) pred).

Examples:
> (trimf '(1 2 3 a b c 4 5 6) integer?)

'(a b c)

> (trimf '(1 2 3 a b c) integer?)

'(a b c)

> (trimf '(a b c) integer?)

'(a b c)

> (trimf '(a b c 1 2 3 d e f) integer?)

'(a b c 1 2 3 d e f)

procedure

(filter-split lst pred)  (listof list?)

  lst : list?
  pred : procedure?
Like string-split, but for lists. Drop elements from anywhere in lst that satisfy pred — ends, middle, you name it — and return a list of the sublists that remain.

Examples:
> (filter-split '(1 a b c 2 d e f 3) integer?)

'((a b c) (d e f))

> (filter-split '(1 a b c 2 d e f 3) (negate integer?))

'((1) (2) (3))

procedure

(partition* pred lst)  
list? list?
  pred : procedure?
  lst : list?
Like partition, but contiguous groups of elements matching (or not matching) pred are kept together in sublists.

Same as (values (filter-split lst pred) (filter-split lst (negate pred))), but only traverses the list once.

Examples:
> (partition* integer? '(1 a b c 2 d e f 3))

'((1) (2) (3))

'((a b c) (d e f))

> (partition* (negate integer?) '(1 a b c 2 d e f 3))

'((a b c) (d e f))

'((1) (2) (3))

procedure

(slice-at lst len [force?])  (listof list?)

  lst : list?
  len : (and/c integer? positive?)
  force? : boolean? = #f
Divide lst into sublists of length len. If lst cannot be divided evenly by len, the last sublist will be shorter. If this displeases you, set force? to #t and a stumpy final sublist will be ignored.

Examples:
> (slice-at (range 5) 1)

'((0) (1) (2) (3) (4))

> (slice-at (range 5) 2)

'((0 1) (2 3) (4))

> (slice-at (range 5) 2 #t)

'((0 1) (2 3))

> (slice-at (range 5) 3)

'((0 1 2) (3 4))

> (slice-at (range 5) 5)

'((0 1 2 3 4))

> (slice-at (range 5) 5 #t)

'((0 1 2 3 4))

> (slice-at (range 5) 100000)

'((0 1 2 3 4))

> (slice-at (range 5) 100000 #t)

'()

procedure

(slicef lst pred)  (listof list?)

  lst : list?
  pred : procedure?
Divide lst into sublists that are homogeneously pred or not pred. If none of the elements match pred, there is no slice to be made, and the result is the whole input list.

Examples:
> (slicef '(1 2 2 1 2) even?)

'((1) (2 2) (1) (2))

> (slicef (range 5) odd?)

'((0) (1) (2) (3) (4))

> (slicef (range 5) string?)

'((0 1 2 3 4))

procedure

(slicef-at lst pred [force?])  (listof list?)

  lst : list?
  pred : procedure?
  force? : boolean? = #f
Divide lst into sublists, each starting with an element matching pred. The first element of the first sublist may not match pred. But if you really & truly want only the sublists starting with an element matching pred, set force? to #true.

If none of the elements match pred, there is no slice to be made, and the result is the whole input list.

Examples:
> (slicef-at (range 5) even?)

'((0 1) (2 3) (4))

> (slicef-at '(1 2 2 1 2) even?)

'((1) (2) (2 1) (2))

> (slicef-at '(1 2 2 1 2) even? #t)

'((2) (2 1) (2))

> (slicef-at (range 5) odd?)

'((0) (1 2) (3 4))

> (slicef-at (range 5) odd? #t)

'((1 2) (3 4))

procedure

(slicef-after lst pred [force?])  (listof list?)

  lst : list?
  pred : procedure?
  force? : boolean? = #f
Divide lst into sublists, each ending with an element matching pred. The last element of the last sublist may not match pred. But if you really & truly want only the sublists ending with an element matching pred, set force? to #true.

If none of the elements match pred, there is no slice to be made, and the result is the whole input list.

Examples:
> (slicef-after '(1 2 2 1 2) even?)

'((1 2) (2) (1 2))

> (slicef-after (range 5) odd?)

'((0 1) (2 3) (4))

> (slicef-after (range 5) odd? #true)

'((0 1) (2 3))

> (slicef-after (range 5) string?)

'((0 1 2 3 4))

procedure

(frequency-hash lst)  hash?

  lst : list?
Count the frequency of each element in lst, and return a hash whose keys are the unique elements of lst, and each value is the frequency of that element within lst.

Examples:
> (frequency-hash '(a b b c c c))

'#hash((a . 1) (b . 2) (c . 3))

> (frequency-hash '(c b c a b c))

'#hash((a . 1) (b . 2) (c . 3))

procedure

(members-unique? container)  boolean?

  container : (or/c list? vector? string?)
Return #t if every element in container is unique, otherwise #f.

Examples:
> (members-unique? '(a b c d e f))

#t

> (members-unique? '(a b c d e f a))

#f

procedure

(members-unique?/error container)  boolean?

  container : (or/c list? vector? string?)
Same as members-unique?, but if the members are not unique, raises a descriptive error rather than returning #f.

Examples:
> (members-unique?/error '(a b c d e f))

#t

> (members-unique?/error '(a b c d e f a))

members-unique? failed because item isn't unique: '(a)

> (members-unique?/error '(a b c d e f a b))

members-unique? failed because items aren't unique: '(a b)

syntax

(values->list values)

Convert values to a simple list.

Examples:
> (split-at '(a b c d e f) 3)

'(a b c)

'(d e f)

> (values->list (split-at '(a b c d e f) 3))

'((a b c) (d e f))

procedure

(sublist lst start-idx end-idx)  list?

  lst : list?
  start-idx : (and/c integer? (not/c negative?))
  end-idx : (and/c integer? (not/c negative?))
Return a sublist of the lst starting with item start-idx and ending one item before item end-idx. (Similar to how list slices are denominated in Python.) Thus the maximum value for end-idx is (length lst). Errors will be triggered by nonsensical values for end-idx.

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:
> (sublist '(0 1 2 3 4 5 6 7 8) 0 8)

'(0 1 2 3 4 5 6 7)

> (sublist '(0 1 2 3 4 5 6 7 8) 8 9)

'(8)

> (sublist '(0 1 2 3 4 5 6 7 8) 2 5)

'(2 3 4)

> (sublist '(0 1 2 3 4 5 6 7 8) 5 2)

format: format string requires 0 arguments, given 1;

arguments were: '(5 2)

> (sublist '(0 1 2 3 4 5 6 7 8) 2 10)

sublist: ending index 10 exceeds length of list

procedure

(break-at lst indexes)  (listof list?)

  lst : list?
  indexes : (or/c integer? (listof integer?))
Break lst into smaller lists at the index positions in indexes. If a single integer value is given for indexes, it’s treated as a one-element list. Errors will arise if a breakpoint index exceeds the length of the list, or if the breakpoints are not increasing.

Examples:
> (break-at '(0 1 2 3 4 5 6 7 8) 3)

'((0 1 2) (3 4 5 6 7 8))

> (break-at '(0 1 2 3 4 5 6 7 8) '(3))

'((0 1 2) (3 4 5 6 7 8))

> (break-at '(0 1 2 3 4 5 6 7 8) '(3 6))

'((0 1 2) (3 4 5) (6 7 8))

> (break-at '(0 1 2 3 4 5 6 7 8) '(3 6 8))

'((0 1 2) (3 4 5) (6 7) (8))

> (break-at '(0 1 2 3 4 5 6 7 8) '(3 6 8 10))

break-at: contract violation

  expected: breakpoints not greater than or equal to input

list length = 9

  given: '(3 6 8 10)

procedure

(shift lst how-far [fill-item cycle?])  list?

  lst : list?
  how-far : integer?
  fill-item : any/c = #f
  cycle? : boolean? = #f
Move the items in lst to the right (if how-far is positive) or left (if how-far is negative). By default, vacated spaces in the list are filled with fill-item. But if cycle? is true, elements of the list wrap around (and fill-item is ignored). Either way, the result list is always the same length as the input list. (If you don’t care about the lengths being the same, you probably want take or drop instead.) If how-far is 0, return the original list. If how-far is bigger than the length of lst, and cycle is not true, raise an error.

Examples:
> (define xs (range 5))
> (shift xs 2)

'(#f #f 0 1 2)

> (shift xs -2 0)

'(2 3 4 0 0)

> (shift xs 2 'boing)

'(boing boing 0 1 2)

> (shift xs 2 'boing #t)

'(3 4 0 1 2)

> (shift xs 0)

'(0 1 2 3 4)

> (shift xs 42)

shift: contract violation

  expected: index not larger than list length 5

  given: 42

procedure

(shift-left lst how-far [fill-item cycle?])  list?

  lst : list?
  how-far : integer?
  fill-item : any/c = #f
  cycle? : boolean? = #f
Like shift, but the list is shifted left when how-far is positive, and right when it’s negative. Otherwise identical.

Examples:
> (define xs (range 5))
> (shift-left xs 2)

'(2 3 4 #f #f)

> (shift-left xs -2 0)

'(0 0 0 1 2)

> (shift-left xs 2 'boing)

'(2 3 4 boing boing)

> (shift-left xs 2 'boing #t)

'(2 3 4 0 1)

> (shift-left xs 0)

'(0 1 2 3 4)

> (shift-left xs 42)

shift-left: contract violation

  expected: index not larger than list length 5

  given: 42

procedure

(shift-cycle lst how-far)  list?

  lst : list?
  how-far : integer?

procedure

(shift-left-cycle lst how-far)  list?

  lst : list?
  how-far : integer?
Like shift and shift-left, but automatically invokes cycle mode. how-far can be any size.

Examples:
> (define xs (range 5))
> (shift-cycle xs 2)

'(3 4 0 1 2)

> (shift-cycle xs -2)

'(2 3 4 0 1)

> (shift-cycle xs 0)

'(0 1 2 3 4)

> (shift-cycle xs 42)

'(3 4 0 1 2)

> (shift-left-cycle xs 2)

'(2 3 4 0 1)

> (shift-left-cycle xs -2)

'(3 4 0 1 2)

> (shift-left-cycle xs 0)

'(0 1 2 3 4)

> (shift-left-cycle xs 42)

'(2 3 4 0 1)

procedure

(shifts lst how-far [fill-item cycle?])  (listof list?)

  lst : list?
  how-far : (listof integer?)
  fill-item : any/c = #f
  cycle? : boolean? = #f
Same as shift, but how-far is a list of integers rather than a single integer, and the result is a list of lists rather than a single list.

Examples:
> (define xs (range 5))
> (shifts xs '(-2 2))

'((2 3 4 #f #f) (#f #f 0 1 2))

> (shifts xs '(-2 2) 0)

'((2 3 4 0 0) (0 0 0 1 2))

> (shifts xs '(-2 2) 'boing)

'((2 3 4 boing boing) (boing boing 0 1 2))

> (shifts xs '(-2 2) 'boing #t)

'((2 3 4 0 1) (3 4 0 1 2))

procedure

(shift/values lst how-far [fill-item])  any

  lst : list?
  how-far : (or/c integer? (listof integer?))
  fill-item : any/c = #f
When how-far is a single integer, same as shift, but the resulting list is returned as values. When how-far is a list of integers, same as shifts, but the resulting lists are returned as multiple values rather than as a list of lists.

Examples:
> (define xs (range 5))
> (shift xs 1)

'(#f 0 1 2 3)

> (shift/values xs 1)

#f

0

1

2

3

> (shifts xs '(-1 0 1))

'((1 2 3 4 #f) (0 1 2 3 4) (#f 0 1 2 3))

> (shift/values xs '(-1 0 1))

'(1 2 3 4 #f)

'(0 1 2 3 4)

'(#f 0 1 2 3)

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

procedure

(xml-string->xexprs xml-string)  
xexpr? xexpr?
  xml-string : string?
Take a string containg XML and break it into two X-expressions: one representing the prolog of the document, and the other representing everything under the root node. Your xml-string must have a root node, but it doesn’t need a prolog.

Examples:
> (define str "<?xml encoding=\"utf-8\"?>\n<root>hello</root>")
> (xml-string->xexprs str)

(prolog

 (list (p-i (location 1 0 1) (location 1 24 25) 'xml "encoding=\"utf-8\""))

 #f

 '())

'(root () "hello")

> (define root-only "<root>hello</root>")
> (xml-string->xexprs root-only)

(prolog '() #f '())

'(root () "hello")

> (define prolog-only "<?xml encoding=\"utf-8\"?>")
> (xml-string->xexprs prolog-only)

read-xml: parse-error: expected root element - received

#<eof>

procedure

(xexprs->xml-string prolog-xexpr    
  root-xexpr)  string?
  prolog-xexpr : xexpr?
  root-xexpr : xexpr?
Take two X-expressions representing the prolog and root of an XML document and join them back into an XML string. In other words, the inverse of the function above.

Examples:
> (define str "<?xml encoding=\"utf-8\"?>\n<root>hello</root>")
> (define-values (prolog doc) (xml-string->xexprs str))
> prolog

(prolog

 (list (p-i (location 1 0 1) (location 1 24 25) 'xml "encoding=\"utf-8\""))

 #f

 '())

> doc

'(root () "hello")

> (xexprs->xml-string prolog doc)

"<?xml encoding=\"utf-8\"?>\n<root>hello</root>"

8 License & source code🔗ℹ

This module is licensed under the LGPL.

Source repository at http://github.com/mbutterick/sugar. Suggestions & corrections welcome.