Soup:   A library of useful routines
1 Top level interface
2 Additional functions for paths and files
make-directory*
delete-file*
delete-directory*
directory-tree
3 Additional for and for* style comprehensions
3.1 for loops
for/  max
for*/  max
for/  min
for*/  min
for/  string
for*/  string
for/  bytes
for*/  bytes
for/  list/  mv
for*/  list/  mv
for/  count
for*/  count
3.2 Sequences
in-char-range
in-conses
4 Hash Table functions
hash->vector
hash-keys/  vector
hash-values/  vector
hash->immutable-hash
5 List-related functions
lmax
lmin
chunk
slice
5.1 Common Lisp functions
subst
subst-if
adjoin
maplist
append-maplist
tail?
ldiff
copy-tree
tree-equal?
reuse-cons
collecting
collect
with-collector
with-collectors
5.2 Association List functions
sublis
rassoc
rassoc-if
pairlis
alist-map
alist-for-each
6 Tree functions
copy-tree
tree-equal?
subst
subst-if
walk-tree
map-tree
leaf-walk
leaf-map
occurs-if
occurs
prune-if
prune
7 String functions
string-join/  vector
string->vector
vector->string
string-sort
string-sort!
string-escape
8 JSON functions
->jsexpr
struct->jsexpr?
gen:  struct->jsexpr
json-match
9 Parameter extensions
define-parameter
define-boolean-parameter
10 Math functions
integer-log
11 Control functions
let/  comp
named-let-values
named-lambda
11.1 Common Lisp forms
lret
lret*
if-let
when-let
block
return
return-from
do
do*
dotimes
dolist
prog
prog*
12 Vector functions
vector-shuffle
vector-shuffle!
fxvector-sort!
fxvector-sort
13 I/  O functions
read-bytes-up-to
read-string-up-to
8.12

Soup: A library of useful routines🔗ℹ

A collection of useful functions not important enough to spin off into their own packages.

1 Top level interface🔗ℹ

 (require soup-lib) package: soup-lib

Provides all the functions exported by the modules below.

2 Additional functions for paths and files🔗ℹ

 (require soup-lib/files) package: soup-lib

procedure

(make-directory* dirname [permissions])  boolean?

  dirname : path-string?
  permissions : (integer-in 0 65535) = 511
Create a directory without raising an error if it can’t be.

Returns true if the directory was created, false on failure.

procedure

(delete-file* filename)  boolean?

  filename : path-string?
Deletes a file without raising an error if it can’t be.

Returns true if the file was deleted, false on failure.

procedure

(delete-directory* dirname)  boolean?

  dirname : path-string?
Deletes a directory without raising an error if it can’t be.

Returns true if the directory was deleted, false on failure.

procedure

(directory-tree dirname 
  [#:follow-links? follow-links?]) 
  (listof (flat-rec-contract entry path? (cons/c path? (listof entry))))
  dirname : (and/c path-string? directory-exists?)
  follow-links? : any/c = #t
Scans the given directory and returns a list that’s the root of a tree structure of its contents. Each element is either a list where the first entry is the name of a subdirectory and the rest of the list that directory’s contents, or paths of non-directory files. If #:follow-links? is true, symbolic links to directories are traversed, if false, just the name of the link is included.

3 Additional for and for* style comprehensions🔗ℹ

 (require soup-lib/for) package: soup-lib

3.1 for loops🔗ℹ

syntax

(for/max (for-clauses ...) body-or-break ... body)

body must evaluate to a single real number at each iteration, the maximum of which is returned.

syntax

(for*/max (for-clauses) body-or-break ... body)

body must evaluate to a single real number at each iteration, the maximum of which is returned.

syntax

(for/min (for-clauses) body-or-break ... body)

body must evaluate to a single real number at each iteration, the minimum of which is returned.

syntax

(for*/min (for-clauses) body-or-break ... body)

body must evaluate to a single real number at each iteration, the minimum of which is returned.

syntax

(for/string maybe-length (for-clauses) body-or-break ... body)

 
maybe-length = 
  | #:length len
 
  len : exact-nonnegative-integer?
body must evaluate to a string or character at each iteration, all of which are returned as a single string.

The optional #:length argument can be used to give the expected length of the result as an optimization.

syntax

(for*/string (for-clauses) body-or-break ... body)

 
maybe-length = 
  | #:length len
 
  len : exact-nonnegative-integer?
body must evaluate to a string or character at each iteration, all of which are returned as a single string.

The optional #:length argument can be used to give the expected length of the result as an optimization.

syntax

(for/bytes (for-clauses) body-or-break ... body)

 
maybe-length = 
  | #:length len
 
  len : exact-nonnegative-integer?
body must evaluate to a single byte at each iteration, all of which are returned as a bytestring.

The optional #:length argument can be used to give the expected length of the result as an optimization.

syntax

(for*/bytes (for-clauses) body-or-break ... body)

 
maybe-length = 
  | #:length len
 
  len : exact-nonnegative-integer?
body must evaluate to a single byte at each iteration, all of which are returned as a bytestring.

The optional #:length argument can be used to give the expected length of the result as an optimization.

syntax

(for/list/mv (for-clauses) body-or-break ... body)

Like for/list, but the body can return multiple values, all of which are added to the result list.

syntax

(for*/list/mv (for-clauses) body-or-break ... body)

Like for*/list, but the body can return multiple values, all of which are added to the result list.

syntax

(for/count (for-clause ...) body-or-break ... body)

Returns the number of times body evaluates to a true value.

syntax

(for*/count (for-clause ...) body-or-break ... body)

Returns the number of times body evaluates to a true value.

3.2 Sequences🔗ℹ

procedure

(in-char-range start end)  sequence?

  start : char?
  end : char?
Returns a sequence that iterates over the given (inclusive) range of characters. Ascending if the first character is char<=? the second, descending if char>?.

procedure

(in-conses list)  sequence?

  list : list?
Returns a sequence that iterates over the successive pairs of the given list (Instead of the values stored in the cars like in-list).

4 Hash Table functions🔗ℹ

 (require soup-lib/hash) package: soup-lib

procedure

(hash->vector htab)  (vectorof pair?)

  htab : hash?
Returns a vector holding the contents of the hash table as key-value pairs in an unspecified order.

procedure

(hash-keys/vector htab)  vector?

  htab : hash?
Returns a vector holding the keys of the hash table in an unspecified order.

procedure

(hash-values/vector htab)  vector?

  htab : hash?
Returns a vector holding the values of the hash table in an unspecified order.

procedure

(hash->immutable-hash htab)  (and/c hash? immutable?)

  htab : hash?
Returns an immutable version of htab (If htab is already immutable, it’s returned) using the same underlying comparison routine as the original.

5 List-related functions🔗ℹ

 (require soup-lib/list) package: soup-lib

procedure

(lmax list [<])  any/c

  list : list?
  < : (-> any/c any/c any/c) = <
Returns the maximum element of the list per the given comparision function.

procedure

(lmin list [<])  any/c

  list : list?
  < : (-> any/c any/c any/c) = <
Returns the minimum element of the list per the given comparison function.

procedure

(chunk list nsublists)  (listof list?)

  list : list?
  nsublists : exact-positive-integer?
Split a list up into nsublists lists, all but possibly the last equal in size to each other, and return them all in a list.

procedure

(slice list size)  (listof list?)

  list : list?
  size : exact-positive-integer?
Split a list up into lists size elements long and return them all in a list.

5.1 Common Lisp functions🔗ℹ

Stuff from the cons dictionary without Racket or SRFI-1 equivalents. as well as the Serapeum library and others. Sometimes with better names.

procedure

(subst new old tree [#:key key #:test test])  any/c

  new : any/c
  old : any/c
  tree : any/c
  key : (-> any/c any/c) = identity
  test : (-> any/c any/c any/c) = eqv?

procedure

(subst-if new pred? tree [#:key key])  any/c

  new : any/c
  pred? : (-> any/c any/c)
  tree : any/c
  key : (-> any/c any/c) = identity

procedure

(adjoin elem list [#:key key #:test test])  list?

  elem : any/c
  list : list?
  key : (-> any/c any/c) = identity
  test : (-> any/c any/c any/c) = eqv?

procedure

(maplist proc list ...+)  list?

  proc : procedure?
  list : list?

procedure

(append-maplist proc list ...+)  list?

  proc : procedure?
  list : list?

procedure

(tail? obj list)  boolean?

  obj : any/c
  list : (or/c pair? null?)

procedure

(ldiff list obj)  (or/c pair? null?)

  list : (or/c pair? null?)
  obj : any/c

procedure

(copy-tree tree)  any/c

  tree : any/c

procedure

(tree-equal? tree1 tree2 [#:test test])  boolean?

  tree1 : any/c
  tree2 : any/c
  test : (-> any/c any/c any/c) = eqv?

procedure

(reuse-cons x y x-y)  pair?

  x : any/c
  y : any/c
  x-y : pair?

syntax

(collecting body ...)

The classic Lisp "collecting" macro. Executes the body and returns a list made up of the values passed to collect in it.

procedure

(collect)  list?

(collect value ...+)  void?
  value : any/c
Only usable inside collecting; appends the values to that macros’ result list. When called without any arguments, returns a list of the currently collected values.

syntax

(with-collector (collector) body ...)

From Serapeum.

Like collecting but allows a user-defined name instead of collect.

syntax

(with-collectors (collector ...) body ...)

From Serapeum.

Like with-collector but allows multiple different collectors. Returns one value per collector.

5.2 Association List functions🔗ℹ

procedure

(sublis alist tree [#:key key #:test test])  any/c

  alist : (listof pair?)
  tree : any/c
  key : (-> any/c any/c) = identity
  test : (-> any/c any/c any/c) = eqv?

procedure

(rassoc item alist [#:key key #:test test])  (or/c pair? #f)

  item : any/c
  alist : (listof pair?)
  key : (-> any/c any/c) = identity
  test : (-> any/c any/c any/c) = eqv?

procedure

(rassoc-if pred? alist [#:key key])  (or/c pair? #f)

  pred? : (-> any/c any/c)
  alist : (listof pair?)
  key : (-> any/c any/c) = identity

procedure

(pairlis keys values [alist])  any/c

  keys : list?
  values : list?
  alist : any/c = '()

procedure

(alist-map proc alist)  (listof pair?)

  proc : (-> any/c any/c any/c)
  alist : (listof pair?)

Returns a new association list formed by mapping proc over the keys and values of alist. proc must be a function of 2 arguments which returns the new value part.

procedure

(alist-for-each proc alist)  void?

  proc : (-> any/c any/c any)
  alist : (listof pair?)

Applies proc to each pair of keys and values of alist.

6 Tree functions🔗ℹ

Functions for working on trees made of cons cells; mostly taken from Common Lisp. A few of these functions are also provided by soup-lib/list.

 (require soup-lib/tree) package: soup-lib

procedure

(copy-tree tree)  any/c

  tree : any/c

procedure

(tree-equal? tree1 tree2 [#:test test])  boolean?

  tree1 : any/c
  tree2 : any/c
  test : (-> any/c any/c any/c) = eqv?

procedure

(subst new old tree [#:key key #:test test])  any/c

  new : any/c
  old : any/c
  tree : any/c
  key : (-> any/c any/c) = identity
  test : (-> any/c any/c any/c) = eqv?

procedure

(subst-if new pred? tree [#:key key])  any/c

  new : any/c
  pred? : (-> any/c any/c)
  tree : any/c
  key : (-> any/c any/c) = identity

procedure

(walk-tree fun    
  tree    
  [#:tag tag    
  #:traversal traversal])  void?
  fun : (-> any/c any)
  tree : any/c
  tag : (or/c continuation-prompt-tag? #f) = #f
  traversal : (or/c 'preorder 'inorder 'postorder) = 'preorder
From Serapeum.

Call fun in turn over each atom and cons of tree.

fun can skip the current subtree with (abort/cc tag '()).

procedure

(map-tree fun    
  tree    
  [#:tag tag    
  #:traversal traversal])  any/c
  fun : (-> any/c any/c)
  tree : any/c
  tag : (or/c continuation-prompt-tag? #f) = #f
  traversal : (or/c 'preorder 'inorder 'postorder) = 'preorder
From Serapeum.

Walk fun over tree and build a tree from the results.

The new tree may share structure with the old tree.

(eq? tree (map-tree identity tree)) ; #t

fun can skip the current subtree with (abort/cc tag subtree), in which case subtree will be used as the value of the subtree.

procedure

(leaf-walk fun tree)  void?

  fun : (-> any/c any)
  tree : any/c
From Serapeum.

Call fun on each leaf of tree.

procedure

(leaf-map fun tree)  any/c

  fun : (-> any/c any/c)
  tree : any/c
From Serapeum.

Call fun on each leaf of tree. Return a new tree possibly sharing structure with tree.

procedure

(occurs-if test    
  tree    
  [#:key key    
  #:traversal traversal])  
any/c boolean?
  test : (-> any/c any/c)
  tree : any/c
  key : (-> any/c any/c) = identity
  traversal : (or/c 'preorder 'inorder 'postorder) = 'preorder
From Serapeum.

Is there a node (leaf or cons) in tree that satisfies test?

Returns two values - the node that matched (Or undefined if none did) and a boolean indicating if the node was found or not.

procedure

(occurs node    
  tree    
  [#:key key    
  #:test test    
  #:traversal traversal])  
any/c boolean?
  node : any/c
  tree : any/c
  key : (-> any/c any/c) = identity
  test : (-> any/c any/c any/c) = eqv?
  traversal : (or/c 'preorder 'inorder 'postorder) = 'preorder
From Serapeum.

Is node present in tree?

Returns two values - the node that matched (Or undefined if none did) and a boolean indicating if the node was found or not.

procedure

(prune-if test tree [#:key key])  list?

  test : (-> any/c any/c)
  tree : list?
  key : (-> any/c any/c) = identity
From Serapeum.

Remove any atoms satisfying test from tree.

Pruning is defined "modulo flatten": you should get the same result from pruning, and then flattening, that you would get from flattening, and then filtering.

Also note that pruning is not defined for trees containing improper lists.

procedure

(prune leaf tree [#:key key #:test test])  list?

  leaf : any/c
  tree : list?
  key : (-> any/c any/c) = identity
  test : (-> any/c any/c any/c) = eqv?
From Serapeum.

Remove leaf from tree wherever it occurs. See prune-if for more information.

7 String functions🔗ℹ

 (require soup-lib/string) package: soup-lib

procedure

(string-join/vector strs [sep])  string?

  strs : (vectorof string?)
  sep : string? = " "
Like string-join but takes a vector of strings instead of a list of strings.

procedure

(string->vector s)  (vectorof char?)

  s : string?
Like string->list but returns a vector instead.

procedure

(vector->string vc)  string?

  vc : (vectorof char?)
Like list->string but takes a vector instead.

procedure

(string-sort s [<?])  string?

  s : string?
  <? : (-> char? char? any/c) = char<?
Returns a copy of s with its characters sorted according to <?.

procedure

(string-sort! s [<?])  void?

  s : (and/c string? (not/c immutable?))
  <? : (-> char? char? any/c) = char<?
Sorts s in-place.

procedure

(string-escape s mapper [start stop])  string?

  s : string?
  mapper : (or/c dict? (-> char? (or/c string? #f)))
  start : exact-nonnegative-integer? = 0
  stop : exact-nonnegative-integer? = (string-length s)
Inspired by Serapeum’s "escape", replaces characters in s with the string that they map to in the dictionary mapper (Or what it returns if a function).

8 JSON functions🔗ℹ

 (require soup-lib/json) package: soup-lib

procedure

(->jsexpr js)  jsexpr?

  js : (or/c jsexpr? struct->jsexpr?)
When called with a value that’s already a jsexpr?, return it. Otherwise, if called on a struct that implements the gen:struct->jsexpr generic interface, call that to convert it to a jsexpr?.

procedure

(struct->jsexpr? obj)  boolean?

  obj : any/c
Tests if an object is a struct that implements the gen:struct->jsexpr interface.

A generic interface that supplies the ->jsexpr method to convert a struct to a jsepxr? value for use with JSON modules.

(struct example (foo bar)
  #:methods gen:struct->jsexpr
  ([define (->jsexpr ex) (hasheq 'foo (example-foo ex) 'bar (example-bar ex))]))
(->jsexpr (example 1 "cat"))

syntax

(json-match maybe-unsafe jsexpr match-clause ... maybe-else)

 
maybe-unsafe = 
  | #:unsafe
     
match-clause = (number expr ...+)
  | ((number id) expr ...+)
  | (string expr ...+)
  | ((string id) expr ...+)
  | (array expr ...+)
  | ((array id) expr ...+)
  | (object expr ...+)
  | ((object id) expr ...+)
  | (null expr ...+)
  | boolean-clause
     
boolean-clause = exact-boolean-clause ...
  | (boolean expr ...+)
  | ((boolean id) expr ...+)
     
exact-boolean-clause = (true expr ...+)
  | (false expr ...+)
     
maybe-else = 
  | (else expr ...+)
 
  jsexpr : jsexpr?
Conditional evaluation based on the type of a jsexpr value, possibly binding the value to the given identifier. Only one of each particular type can be present. If the type of the given jsexpr is not present and there is no else clause, an error is raised.

If the optional #:unsafe keyword is given, no check is done to make sure jsexpr is actually a jsexpr?.

Example:

(json-match "foo"
  (number 'num)
  (array 'arr)
  (object 'obj)
  (string 'str)
  (else 'other)) ; 'str

9 Parameter extensions🔗ℹ

 (require soup-lib/parameter) package: soup-lib

syntax

(define-parameter id initial-value)

(define-parameter id initial-value guard)
(define-parameter id initial-value guard name)
 
  id : identifier?
  initial-value : any/c
  guard : (or/c (-> any/c any/c) #f)
  name : symbol?
Define a parameter with the given name and value.

The optional guard and name arguments are as in make-parameter. The default name is id instead of 'parameter-procedure, though.

syntax

(define-boolean-parameter id)

(define-boolean-parameter id initial-value)
(define-boolean-parameter id initial-value name)
 
  id : identifier?
  initial-value : boolean?
  name : symbol?
Define a boolean parameter with the given name. The default initial value if not given is #t. Any value can be used to set the parameter, but it’s converted to a boolean; in other words it’s compatible with the contract (parameter/c any/c boolean?).

The optional name argument is as in make-parameter. The default name is id instead of 'parameter-procedure, though.

10 Math functions🔗ℹ

 (require soup-lib/math) package: soup-lib

Written in Typed Racket.

procedure

(integer-log base num)  exact-integer?

  base : exact-integer?
  num : exact-integer?
Computes the integer logarithm base base of num. Uses Oleg Kiselyov’s fast algorithm ported from Haskell to get good performance on even very very large numbers.

11 Control functions🔗ℹ

 (require soup-lib/control) package: soup-lib

syntax

(let/comp maybe-prompt k body ...+)

 
maybe-prompt = 
  | #:prompt prompt-tag
 
  prompt-tag : continuation-prompt-tag?

syntax

(named-let-values name ([(id ...) producer] ...) body ...+)

 
  name : identifier?
  id : identifier?
Like a named let, but the initial values for ids are obtained from the values returned by evaluating producers. When recursing in the body, arguments correspond to ids going left to right top to bottom.

Example:

(named-let-values loop ([(a b) (values 1 2)]) (if (= b 10) a (loop (+ a b) (+ b 1))))

syntax

(named-lambda (name kw-formals ...) body ...+)

 
  name : identifier?
From R2RS; equivalent to

(letrec ((name (lambda (kw-formals ...) body ...))) name)

Also see rec from SRFI-31.

11.1 Common Lisp forms🔗ℹ

Things taken from Common Lisp. Implicit blocks are supported, implicit tagbodies are not.

syntax

(lret ([id init] ...) body ...)

Like let, but it returns the values of the bindings after executing the body. From Serapeum.

syntax

(lret* ([id init] ...) body ...)

Like let*, but it returns the values of the bindings after executing the body. From Serapeum.

syntax

(if-let ([id init] ...) true-case false-case)

First binds values to ids like let, and if all are truthy, executes true-case, otherwise false-case. All bindings are visible in both cases. From Alexandria.

syntax

(when-let ([id init] ...) body ...+)

First binds values to ids like let, and if all are truthy, executes body. Returns the value(s) of the last expression in body, or void. From Alexandria.

syntax

(block name body ...)

 
  name : identifier?

syntax

(return)

(return result)

syntax

(return-from name)

(return-from name result)
 
  name : identifier?

syntax

(do ((var init incr) ...) (end-case result ...) body ...)

Basically, normal Racket do extended to be in a block that can be returned from early. See Common Lisp do for more.

syntax

(do* ((var init incr) ...) (end-case result ...) body ...)

Like do but with let* style scoping. See Common Lisp do* for more.

syntax

(dotimes (var count-form maybe-result) body ...)

 
maybe-result = 
  | expr
 
  var : identifier?

See Common Lisp dotimes.

syntax

(dolist (var list-form maybe-result) body ...)

 
maybe-result = 
  | expr
 
  var : identifier?

syntax

(prog (variable-declaration ...) body ...)

 
variable-declaration = var
  | (var)
  | (var init-form)
 
  var : identifier?

See Common Lisp prog.

syntax

(prog* (variable-declaration ...) body ...)

 
variable-declaration = var
  | (var)
  | (var init-form)
 
  var : identifier?

See Common Lisp prog*.

12 Vector functions🔗ℹ

 (require soup-lib/vector) package: soup-lib

procedure

(vector-shuffle vec [start end])  vector?

  vec : vector?
  start : exact-nonnegative-integer? = 0
  end : exact-nonnegative-integer? = (vector-length vec)
Returns a newly allocated copy of the given range of vec, shuffled in random order.

procedure

(vector-shuffle! vec [start end])  void?

  vec : (and/c vector? (not/c immutable?))
  start : exact-nonnegative-integer? = 0
  end : exact-nonnegative-integer? = (vector-length vec)
Shuffles the given range of the mutable vector in-place.

procedure

(fxvector-sort! vec [start end])  void?

  vec : fxvector?
  start : exact-nonnegative-integer? = 0
  end : exact-nonnegative-integer? = (fxvector-length vec)
Sorts the given range of a fxvector inplace, in ascending order.

procedure

(fxvector-sort vec [start end])  fxvector?

  vec : fxvector?
  start : exact-nonnegative-integer? = 0
  end : exact-nonnegative-integer? = (fxvector-length vec)
Returns a newly allocated copy of the given range of the fxvector, sorted in ascending order.

13 I/O functions🔗ℹ

 (require soup-lib/port) package: soup-lib

procedure

(read-bytes-up-to port delim)  (or/c bytes? eof-object?)

  port : input-port?
  delim : byte?
Like read-bytes-line but allows a custom delimiter byte.

procedure

(read-string-up-to port delim)  (or/c string? eof-object?)

  port : input-port?
  delim : char?
Like read-line but allows a custom delimiter character.