Lathe Comforts
1 Evergreen Utilities for Binding Syntax and Pure FP
1.1 Utilities for Binding Syntax
binds
define-simple-normalizing-binder
1.2 Utilities for Functional Programming
1.2.1 Bindings and recursion
pass
w-
fn
w-loop
loopfn
1.2.2 Conditionals
mat
expect
matfns
expectfn
dissect
dissect/  derived
dissectfn
dissectfn/  derived
2 Utilities for Contracts
let/  c
fix/  c
by-own-method/  c
3 Maybe Values
nothing
nothing?
just
just?
just-value
maybe?
maybe/  c
maybe-bind
maybe-map
maybe-if
4 Utilities for Strings
immutable-string?
5 Trivial Values
trivial
trivial?
6 Utilities for Lists and Natural Numbers
6.1 Utilities for Natural Numbers
nat->maybe
6.2 Utilities for Lists
list-foldl
list-foldr
list-bind
list-map
list-any
list-all
list-each
list-kv-map
list-kv-any
list-kv-all
list-kv-each
list-zip-map
list-zip-any
list-zip-all
list-zip-each
6.3 Utilities for Natural Numbers and Lists Together
list-length<=nat?
nat<=list-length?
list-length=nat?
list-length<nat?
nat<list-length?
7 Utilities for Hash Tables
make-similar-hash
hash-comparison-same?
hash-keys-same?
hash-ref-maybe
hash-set-maybe
hash-kv-map-sorted
hash-kv-bind
hash-kv-map-maybe
hash-kv-map
hash-kv-any
hash-kv-all
hash-kv-each
hash-v-map-maybe
hash-v-map
hash-v-any
hash-v-all
hash-v-each
8 Utilities for Structs
struct-easy
struct-predicate
struct-accessor-by-name
istruct/  c
define-imitation-simple-struct
auto-write
auto-equal
define-imitation-simple-generics
9 Utilities for Match Expanders
define-match-expander-from-match-and-make
define-match-expander-attenuated
match/  c
7.4

Lathe Comforts

Lathe Comforts for Racket is a collection of utilities that are handy for writing Racket code. This is a non-intrusive toolkit; in most cases it should only make certain Racket code easier to write, without substantially changing the architecture of the project it’s used in.

Some of these utilities are designed with Parendown in mind. In some cases, Parendown’s weak opening brackets make it easier to get by with higher-order functions instead of custom syntax. (Note that due to limitations of Scribble’s Racket code formatter, we use Parendown’s ‘pd‘ macro to achieve these weak parens, rather than using its custom reader syntax.)

    1 Evergreen Utilities for Binding Syntax and Pure FP

      1.1 Utilities for Binding Syntax

      1.2 Utilities for Functional Programming

        1.2.1 Bindings and recursion

        1.2.2 Conditionals

    2 Utilities for Contracts

    3 Maybe Values

    4 Utilities for Strings

    5 Trivial Values

    6 Utilities for Lists and Natural Numbers

      6.1 Utilities for Natural Numbers

      6.2 Utilities for Lists

      6.3 Utilities for Natural Numbers and Lists Together

    7 Utilities for Hash Tables

    8 Utilities for Structs

    9 Utilities for Match Expanders

1 Evergreen Utilities for Binding Syntax and Pure FP

 (require lathe-comforts) package: lathe-comforts-lib

1.1 Utilities for Binding Syntax

splicing syntax class

binds

Matches syntax in any of three formats:

In all cases, this binds two attributes of ellipsis depth 1, namely var and val, and they carry the same number of matches.

(See _, expr, and id.)

syntax

(define-simple-normalizing-binder (id pattern ...)
  (template ...))
Defines a syntax transformer named id which "normalizes" its binding syntax. Its input is a form that uses the rather permissive binds splicing syntax class, and its output specifically uses the ([var val] ...) binding format expected by most Racket binding syntaxes.

Specifically, the generated macro is equivalent to the following, where pattern ... and template ... are expanded right away, and the rest of the ellipses are part of the generated macro:

(define-simple-macro (id pattern ... vars:binds body:expr ...)
  (template ... ([vars.var vars.val] ...)
    body ...))

(See expr.)

As an example, w- and w-loop are defined straightforwardly in terms of let:

(define-simple-normalizing-binder (w-)
  (let))
(define-simple-normalizing-binder (w-loop proc:id)
  (let proc))

1.2 Utilities for Functional Programming

1.2.1 Bindings and recursion

procedure

(pass arg func)  any

  arg : any/c
  func : (-> any/c any)
Invokes the given procedure with the given argument value. In other words, (pass arg func) is just like (func arg) but in a different order.

This utility can come in handy when experimenting with a new operation that returns procedures—for example, match-lambda. Instead of going to the trouble to define another operation that acts as a let binding—in this case, matchit’s easy enough to use pass and a weak bracket to accomplish basically the same programming style:

> (match (list 1 2 3)
    [(list) #f]
    [(cons first rest) rest])

'(2 3)

> (pd / pass (list 1 2 3) / match-lambda
    [(list) #f]
    [(cons first rest) rest])

'(2 3)

syntax

(w- local-binds body-expr ...)

Works just like a let form with no proc-id, but uses the binds splicing syntax class for the syntax of its bindings, so parentheses can usually be omitted.

Examples:
> (w- ([a 1] [b 2])
    (+ a b))

3

> (w- [a 1 b 2]
    (+ a b))

3

> (w- a 1 b 2
    (+ a b))

3

syntax

(fn arg-id ... body-expr)

Creates a procedure with positional arguments arg-id ... and body body-expr.

This is only a frequently useful shorthand, not a full replacement of lambda. Unlike lambda, fn can only be used to create functions of fixed arity, with no keyword arguments, and the body may only consist of one expression (although this expression may be a begin form of course). Hence, programs that use fn may still need to use lambda on occasion.

Examples:
> (pd / hash-map (hash 'a 1 'b 2) / fn k v
    (format "(~s, ~s)" k v))

'("(a, 1)" "(b, 2)")

> (pd / build-list 5 / fn ~ / * 10 ~)

'(0 10 20 30 40)

syntax

(w-loop proc-id local-binds body ...)

Works just like a let form with a proc-id, but uses the binds splicing syntax class for the syntax of its bindings, so parentheses can usually be omitted.

This example reverses and squares the numbers in a list, using the next procedure to continue the loop:

> (pd / w-loop next original (list 1 2 3) result (list)
    (expect original (cons first rest) result
    / next rest / cons (* first first) result))

'(9 4 1)

syntax

(loopfn proc-id arg-id ... body-expr)

Creates a procedure with positional arguments arg-id ... and body body-expr, which can refer to itself in the body using the name proc-id.

1.2.2 Conditionals

syntax

(mat val-expr pat then-expr else-expr)

syntax

(expect val-expr pat else-expr then-expr)

Checks whether pat matches the result of val-expr. If it does, this evaluates then-expr in tail position with the bindings introduced by pat. Otherwise, this evaluates else-expr without any new bindings.

The only difference between mat and expect is the order of then-expr and else-expr in the form. When these are used with Parendown’s weak opening brackets, they enable a programming style where run time error checking and other early exit conditions are kept toward the top of a procedure body, without affecting the indentation of the procedure’s main logic.

Examples:
> (pd / define (rev lst)
    (w-loop next lst lst result (list)
  
  
      (mat lst (list) result
  
  
  
      / expect lst (cons first rest)
        (error "Expected a list")
  
  
  
      / next rest / cons first result)))
> (rev (list 1 2 3))

'(3 2 1)

> (rev 3)

Expected a list

syntax

(matfns pat then-expr elsefn-expr)

 
  elsefn-expr : (-> any/c any)
Returns a procedure. The procedure takes a single argument value and checks whether it matches the match pattern pat. If it does, the procedure evaluates then-expr in tail position with the bindings introduced by pat. Otherwise, the procedure makes a tail call to the procedure resulting from elsefn-expr, passing in the same argument value.

syntax

(expectfn pat else-expr then-expr)

Returns a procedure. The procedure takes a single argument value and checks whether it matches the match pattern pat. If it does, the procedure evaluates then-expr in tail position with the bindings introduced by pat. Otherwise, the procedure evaluates else-expr in tail position without any new bindings.

syntax

(dissect val-expr pat then-expr)

syntax

(dissect/derived orig val-expr pat then-expr)

Checks whether pat matches the result of val-expr. If it does, this evaluates then-expr in tail position with the bindings introduced by pat. Otherwise, the exn:misc:match? exception is raised.

The dissect/derived variant reports errors in terms of orig.

If you need a custom error message, use expect with an expression that raises an exeption.

syntax

(dissectfn pat then-expr)

syntax

(dissectfn/derived orig pat then-expr)

Returns a procedure. The procedure takes a single argument value and checks whether it matches the match pattern pat. If it does, the procedure evaluates then-expr in tail position with the bindings introduced by pat. Otherwise, the exn:misc:match? exception is raised.

The dissectfn/derived variant reports errors in terms of orig.

If you need a custom error message, use expectfn with an expression that raises an exeption.

2 Utilities for Contracts

 (require lathe-comforts/contract)
  package: lathe-comforts-lib

syntax

(let/c [var-id val-expr] ... body-expr)

 
  val-expr : contract?
  body-expr : contract?
Evaluates each val-expr, renames each of those resulting contracts to its respective 'var-id, evaluates the body-expr with those values in scope under respective var-id variables, and renames the resulting contract to `(let/c [var-id ,val-name] ... ,body-name), where val-name and body-name are the original names of the contracts.

This can come in handy when composiing relatively large contracts that use the same contract in multiple places. It keeps the name more concise than it usually would be.

syntax

(fix/c id options ... contract-expr)

 
  contract-expr : contract?
A fixed-point syntax for contracts. Returns the result of running contract-expr with a certain contract in scope as id. The contract functionality of id should be used only after contract-expr has returned a contract, and it behaves just like that contract. This functionality is based on recursive-contract, and the options given here supply the optional arguments of the recursive-contract operation.

Example:
> (fix/c simple-s-expression/c
    (or/c symbol? (listof simple-s-expression/c)))

#<contract: (fix/c simple-s-expression/c (or/c symbol? (listof simple-s-expression/c)))>

syntax

(by-own-method/c pat body-expr)

 
  body-expr : contract?
A syntax for contracts that depend on the value they apply to. Returns a contract that tries to match the value against the match pattern pat, and if successful, executes the body-expr (with all the bound variables from pat in scope) and behaves according to that contract. If the match is not successful, then the value is not considered to meet this contract.

The name of the returned contract includes pat and body-expr verbatim. If they contain references to variables defined elsewhere, let/c may be useful to ensure those variable bindings are apparent in the overall contract name.

3 Maybe Values

 (require lathe-comforts/maybe)
  package: lathe-comforts-lib

Maybe values are a way to encode optional data. Using maybe values can simplify some interfaces that would otherwise use run time errors or special-cased sentinel values like #f.

syntax

nothing

syntax

(nothing)

match expander

(nothing)

procedure

(nothing? v)  boolean?

  v : any/c
Struct-like operations which construct and deconstruct a maybe value that does not contain an element.

Every two nothing values are equal?.

syntax

just

syntax

(just value-expr)

match expander

(just value-pat)

procedure

(just? v)  boolean?

  v : any/c

procedure

(just-value inst)  any/c

  inst : just?
Struct-like operations which construct and deconstruct a maybe value that contains an element.

Two just values are equal? if they contain equal? elements.

procedure

(maybe? v)  boolean?

  v : any/c
Returns whether the given value is a maybe value. That is, it checks that the value is either a nothing? value or a just? value.

procedure

(maybe/c c)  contract?

  c : contract?
Returns a contract that recognizes a maybe value where the element, if any, abides by the given contract.

procedure

(maybe-bind m func)  maybe?

  m : maybe?
  func : (-> any/c maybe?)
Creates a maybe value by replacing the element of the given maybe value, if any, with zero or one elements according to the given function.

procedure

(maybe-map m func)  maybe?

  m : maybe?
  func : (-> any/c any/c)
Creates a maybe value by replacing the element of the given maybe value, if any, with another according to the given function.

procedure

(maybe-if condition get-value)  maybe?

  condition : any/c
  get-value : (-> any/c)
Creates a maybe value that has an element if and only if the given condition is not #f. The element is computed by calling the given function get-value with no arguments.

4 Utilities for Strings

 (require lathe-comforts/string)
  package: lathe-comforts-lib

procedure

(immutable-string? v)  boolean?

  v : any/c
Returns whether the given value is an immutable string.

Equivalent to (and (string? v) (immutable? v)).

5 Trivial Values

 (require lathe-comforts/trivial)
  package: lathe-comforts-lib

Some values never really vary at all. Perhaps some library accepts an argument that it’ll pass through, but the library’s client has no need for its pass-through services this time. Perhaps some data structure can store annotations on certain nodes, but the client doesn’t really care to annotate any of the nodes this time. In cases like these, it’s useful to have a particular value that doesn’t mean anything.

Racket programs sometimes use (void) for this purpose, but that value is more commonly used as the return value of side-effecting operations which will never have a meaningful result to print at the top level. If a user exploring at the top level uses an operation that typically returns a pass-through value or label, but in this case it happens to return a trivial pass-through value or a trivial label, that’s potentially interesting information for the user, since they may not have even known they were dealing with trivial data yet.

So Lathe Comforts provides a very simple structure type, trivial, to represent trivial values.

syntax

trivial

syntax

(trivial)

match expander

(trivial)

procedure

(trivial? v)  boolean?

  v : any/c
Struct-like operations which construct and deconstruct a trivial value.

Every two trivial values are equal?.

6 Utilities for Lists and Natural Numbers

 (require lathe-comforts/list)
  package: lathe-comforts-lib

6.1 Utilities for Natural Numbers

procedure

(nat->maybe n)  (maybe/c natural?)

  n : natural?
Finds the predecessor of the given natural number, if any. If there is one, the result is a just? of that value. If there isn’t, the result is a nothing?.

6.2 Utilities for Lists

procedure

(list-foldl state lst func)  any/c

  state : any/c
  lst : list?
  func : (-> any/c any/c any/c)
Returns the final state value obtained by initializing the state as the given value state and updating the state by calling the given function func with each element of the list lst from first to last.

The function func is passed two values: The current state value and the list element to process. Its return value is used as the updated state value.

Racket already provides a foldl function, but this one arranges the function to be the last argument.

procedure

(list-foldr lst state func)  any/c

  lst : list?
  state : any/c
  func : (-> any/c any/c any/c)
Returns the final state value obtained by initializing the state as the given value state and updating the state by calling the given function func with each element of the list lst from last to first.

The function func is passed two values: The list element to process and the current state value. Its return value is used as the updated state value.

Racket already provides a foldr function, but this one arranges the function to be the last argument, and it switches the order of the list and initial state arguments so the initial state is visually closer to the side of the list it interacts with.

procedure

(list-bind lst func)  list?

  lst : list?
  func : (-> any/c list?)
Creates a list by replacing every element of the given list with zero, one, or more elements according to the given function.

The elements are processed from first to last.

Racket already provides an append-map function, but this one arranges the function to be the last argument.

procedure

(list-map lst func)  list?

  lst : list?
  func : (-> any/c any/c)
Creates a list by replacing every element of the given list with another according to the given function.

The elements are processed from first to last.

Racket already provides a map function, but this one arranges the function to be the last argument.

procedure

(list-any lst func)  any

  lst : list?
  func : (-> any/c any)
Iterates over the given list’s elements from first to last and calls the given function on each one, stopping early if the function returns a non-#f value for any but the last element.

If the list is empty, the overall result is #f. Otherwise, if the function does return a non-#f value for any but the last element, then that value is used as the overall result. Otherwise, the (possibly multiple) return values of calling the function with the last element are used as the result.

Racket already provides an ormap function, but this one arranges the function to be the last argument.

procedure

(list-all lst func)  any

  lst : list?
  func : (-> any/c any)
Iterates over the given list’s elements from first to last and calls the given function on each one, stopping early if the function returns #f for any but the last element.

If the list is empty, the overall result is #t. Otherwise, if the function does return #f for any but the last element, then the overall result is #f. Otherwise, the (possibly multiple) return values of calling the function with the last element are used as the result.

Racket already provides an andmap function, but this one arranges the function to be the last argument.

procedure

(list-each lst func)  void?

  lst : list?
  func : (-> any/c any)
Iterates over the given list’s elements from first to last and calls the given procedure on each one. Ignores the procedure’s results.

Racket already provides a for-each function, but this one arranges the function to be the last argument.

procedure

(list-kv-map lst func)  list?

  lst : list?
  func : (-> natural? any/c any/c)
Returns a list constructed by iterating over the given list’s entries from first to last, calling the given function with each entry’s index and value, and collecting the results.

procedure

(list-kv-any lst func)  any

  lst : list?
  func : (-> natural? any/c any)
Iterates over the given list’s entries from first to last and calls the given function on each entry’s index and value, stopping early if the function returns a non-#f value for any but the last element.

If the list is empty, the overall result is #f. Otherwise, if the function does return a non-#f value for any but the last entry, then that value is used as the overall result. Otherwise, the (possibly multiple) return values of calling the function with the last entry’s index and value are used as the result.

procedure

(list-kv-all lst func)  any

  lst : list?
  func : (-> natural? any/c any)
Iterates over the given list’s entries from first to last and calls the given function on each entry’s index and value, stopping early if the function returns #f for any but the last element.

If the list is empty, the overall result is #t. Otherwise, if the function does return #f for any but the last entry, then the overall result is #f. Otherwise, the (possibly multiple) return values of calling the function with the last entry’s index and value are used as the result.

procedure

(list-kv-each lst func)  void?

  lst : list?
  func : (-> natural? any/c any)
Iterates over the given list’s entries from first to last and calls the given procedure on each entry’s index and value. Ignores the procedure’s results.

procedure

(list-zip-map a b func)  list?

  a : list?
  b : list?
  func : (-> any/c any/c any/c)
Creates a list by replacing every pair of elements of the given two lists (which must be of the same length) with a single element according to the given function.

The elements are processed from first to last.

Racket already provides a map function, but this one arranges the function to be the last argument.

procedure

(list-zip-any a b func)  any

  a : list?
  b : list?
  func : (-> any/c any/c any)
Iterates over the given two lists (which must be of the same length) and calls the given function on each pair of elements from first to last, stopping early if the function returns a non-#f value for any but the last pair of elements.

If the lists are empty, the overall result is #f. Otherwise, if the function does return a non-#f value for any but the last pair of elements, then that value is used as the overall result. Otherwise, the (possibly multiple) return values of calling the function with the last pair of elements are used as the result.

Racket already provides an ormap function, but this one arranges the function to be the last argument.

procedure

(list-zip-all a b func)  any

  a : list?
  b : list?
  func : (-> any/c any/c any)
Iterates over the given two lists (which must be of the same length) and calls the given function on each pair of elements from first to last, stopping early if the function returns #f for any but the last pair of elements.

If the lists are empty, the overall result is #t. Otherwise, if the function does return #f for any but the last pair of elements, then the overall result is #f. Otherwise, the (possibly multiple) return values of calling the function with the last pair of elements are used as the result.

Racket already provides an andmap function, but this one arranges the function to be the last argument.

procedure

(list-zip-each a b func)  void?

  a : list?
  b : list?
  func : (-> any/c any/c any)
Iterates over the given two lists (which must be of the same length) and calls the given procedure on each pair of elements from first to last. Ignores the procedure’s results.

Racket already provides a for-each function, but this one arranges the function to be the last argument.

6.3 Utilities for Natural Numbers and Lists Together

procedure

(list-length<=nat? lst n)  boolean?

  lst : list?
  n : natural?
(nat<=list-length? n lst)  boolean?
  n : natural?
  lst : list?
(list-length=nat? lst n)  boolean?
  lst : list?
  n : natural?
(list-length<nat? lst n)  boolean?
  lst : list?
  n : natural?
(nat<list-length? n lst)  boolean?
  n : natural?
  lst : list?
These procedures compare a given natural number with the length of a given list. This can be more efficient than comparing the number to (length lst) when that length is larger than the number.

7 Utilities for Hash Tables

 (require lathe-comforts/hash)
  package: lathe-comforts-lib

procedure

(make-similar-hash example assocs)  hash?

  example : hash?
  assocs : (listof pair?)
Returns a hash table with the same key-comparison procedure, key-holding strength, and mutability as example but with entries populated from assocs instead. If assocs contains duplicate keys, the last entry in the list takes precedence.

procedure

(hash-comparison-same? a b)  boolean?

  a : hash?
  b : hash?
Returns whether the two given hash tables have the same comparison procedure (equal?, eqv?, or eq?).

procedure

(hash-keys-same? a b)  boolean?

  a : hash?
  b : hash?
Returns whether the two given hash tables have the same set of keys according to their comparison procedure. If the two hash tables don’t even have the same comparison procedure, the exn:fail:contract exception is raised.

If the thread performing this operation is terminated and the given hash tables use eqv? or equal? as their comparison procedure, all current and future operations on those hash tables may block indefinitely.

If either of the given hash tables is modified partway through this operation, or if either one is an equal?-based hash table whose keys have been mutated after insertion, the resulting behavior may be unpredictable.

procedure

(hash-ref-maybe hash key)  maybe?

  hash : hash?
  key : any/c
Looks up the given key in the given hash table. Returns a just? of the value found or a nothing? if no entry for that key exists.

If the thread performing this operation is terminated and the given hash table uses eqv? or equal? as its comparison procedure, all current and future operations on that hash table may block indefinitely.

If the given hash table is modified partway through this operation, or if it’s an equal?-based hash table whose keys have been mutated after insertion, the resulting behavior may be unpredictable.

procedure

(hash-set-maybe hash key maybe-value)  hash?

  hash : hash?
  key : any/c
  maybe-value : maybe?
Returns a hash table with the same key-comparison procedure, key-holding strength, and mutability as the given one, where the given key’s mapping has been deleted (if maybe-value is a nothing?) or replaced (if it’s a just? of some value to use as the replacement).

If the given hash table is an equal?-based hash table whose keys have been mutated after insertion, the resulting behavior may be unpredictable.

If the result is an equal?-based hash table and one of its keys is mutated after insertion, it may have unpredictable behavior with other hash-table-related utilities.

procedure

(hash-kv-map-sorted key<? hash func)  list?

  key<? : (-> any/c any/c boolean?)
  hash : hash?
  func : (-> any/c any/c any/c)
Returns a list constructed by converting the given hash table to a list, sorting it in ascending order by comparing keys according to the given key<? behavior, and then transforming each entry in ascending order by calling the given func with the entry’s key and value.

The number of times key<? is called is unspecified. The particular argument values it receives are unspecified, aside from the fact that they are all keys of hash.

If the given hash table is modified while this operation is setting up (before it calls either of the given functions for the first time), the particular list of entries it processes may be unpredictable. Modifications after that will not affect the operation.

procedure

(hash-kv-bind hash func)  hash?

  hash : hash?
  func : (-> any/c any/c hash?)
Returns a hash table constructed by iterating over the given hash table hash’s entries in an unspecified order, calling the given function func with each entry’s key and value, and collecting the entries of the resulting hash tables into a single result hash table.

If multiple results of func have the same key, the last one to be computed takes precedence.

The resulting hash table has the same key-comparison procedure, key-holding strength, and mutability as hash. Each result of func may have its own combination of key-comparison procedure, key-holding strength, and mutability; these properties will be ignored.

If hash is modified while this operation is setting up (before it calls the given function for the first time), the particular list of entries this operation processes may be unpredictable. Modifications after that will not affect the operation.

Likewise, if a result of func is modified between the time it is returned and the time this operation either returns or calls func again, then the particular list of entries this operation collects from that func result may be unpredictable.

If the overall result is an equal?-based hash table and one of its keys is mutated after insertion, it may have unpredictable behavior with other hash-table-related utilities.

procedure

(hash-kv-map-maybe hash func)  hash?

  hash : hash?
  func : (-> any/c any/c maybe?)
Returns a hash table constructed by iterating over the given hash table’s entries in an unspecified order, calling the given function with each entry’s key and value, and collecting the results. If for some input entry, the function returns a nothing?, then there is no corresponding output entry. When the function returns a just?, then there is a corresponding output entry which maps the input entry’s key to the the value of the just?.

The resulting hash table has the same key-comparison procedure, key-holding strength, and mutability as the given one.

If the given hash table is modified while this operation is setting up (before it calls the given function for the first time), the particular list of entries it processes may be unpredictable. Modifications after that will not affect the operation.

If the result is an equal?-based hash table and one of its keys is mutated after insertion, it may have unpredictable behavior with other hash-table-related utilities.

procedure

(hash-kv-map hash func)  hash?

  hash : hash?
  func : (-> any/c any/c any/c)
Returns a hash table with the same keys as the given one. The result is constructed by iterating over the given hash table’s entries in an unspecified order and calling the given function with each entry’s key and value to determine the corresponding result entry’s mapped value.

The resulting hash table has the same key-comparison procedure, key-holding strength, and mutability as the given one.

If the given hash table is modified while this operation is setting up (before it calls the given function for the first time), the particular list of entries it processes may be unpredictable. Modifications after that will not affect the operation.

If the result is an equal?-based hash table and one of its keys is mutated after insertion, it may have unpredictable behavior with other hash-table-related utilities.

procedure

(hash-kv-any hash func)  boolean?

  hash : hash?
  func : (-> any/c any/c boolean?)
Iterates over the given hash table’s entries in an unspecified order and calls the given function on each entry’s key and value, stopping early if the function returns #t. If the function does return #t, then the overall result is #t; otherwise, it’s #f.

If the given hash table is modified partway through this operation, the resulting behavior may be unpredictable.

procedure

(hash-kv-all hash func)  boolean?

  hash : hash?
  func : (-> any/c any/c boolean?)
Iterates over the given hash table’s entries in an unspecified order and calls the given function on each entry’s key and value, stopping early if the function returns #f. If the function does return #f, then the overall result is #f; otherwise, it’s #t.

If the given hash table is modified partway through this operation, the resulting behavior may be unpredictable.

procedure

(hash-kv-each hash body)  void?

  hash : hash?
  body : (-> any/c any/c any)
Iterates over the given hash table’s entries in an unspecified order and calls the given procedure on each entry’s key and value. Ignores the procedure’s results.

If the given hash table is modified partway through this operation, the resulting behavior may be unpredictable.

procedure

(hash-v-map-maybe hash func)  hash?

  hash : hash?
  func : (-> any/c maybe?)
Returns a hash table constructed by iterating over the given hash table’s entries in an unspecified order, calling the given function with each entry’s mapped value, and collecting the results. If for some input entry, the function returns a nothing?, then there is no corresponding output entry. When the function returns a just?, then there is a corresponding output entry which maps the input entry’s key to the the value of the just?.

The resulting hash table has the same key-comparison procedure, key-holding strength, and mutability as the given one.

If the given hash table is modified while this operation is setting up (before it calls the given function for the first time), the particular list of entries it processes may be unpredictable. Modifications after that will not affect the operation.

If the result is an equal?-based hash table and one of its keys is mutated after insertion, it may have unpredictable behavior with other hash-table-related utilities.

procedure

(hash-v-map hash func)  hash?

  hash : hash?
  func : (-> any/c any/c)
Returns a hash table with the same keys as the given one. The result is constructed by iterating over the given hash table’s entries in an unspecified order and calling the given function with each entry’s mapped value to determine the corresponding result entry’s mapped value.

The resulting hash table has the same key-comparison procedure, key-holding strength, and mutability as the given one.

If the given hash table is modified while this operation is setting up (before it calls the given function for the first time), the particular list of entries it processes may be unpredictable. Modifications after that will not affect the operation.

procedure

(hash-v-any hash func)  boolean?

  hash : hash?
  func : (-> any/c boolean?)
Iterates over the given hash table’s mapped values in an unspecified order and calls the given function on each one, stopping early if the function returns #t. If the function does return #t, then the overall result is #t; otherwise, it’s #f.

If the given hash table is modified partway through this operation, the resulting behavior may be unpredictable.

procedure

(hash-v-all hash func)  boolean?

  hash : hash?
  func : (-> any/c boolean?)
Iterates over the given hash table’s mapped values in an unspecified order and calls the given function on each one, stopping early if the function returns #f. If the function does return #f, then the overall result is #f; otherwise, it’s #t.

If the given hash table is modified partway through this operation, the resulting behavior may be unpredictable.

procedure

(hash-v-each hash func)  void?

  hash : hash?
  func : (-> any/c any)
Iterates over the given hash table’s mapped values in an unspecified order and calls the given procedure on each one. Ignores the procedure’s results.

If the given hash table is modified partway through this operation, the resulting behavior may be unpredictable.

8 Utilities for Structs

 (require lathe-comforts/struct)
  package: lathe-comforts-lib

syntax

(struct-easy (name-id slot-id ...) rest ...)

This is a convenience layer over struct, which has some features to automate the implementation of the struct’s print behavior and equal? behavior.

The interface is rather unstable at this point. It’s not even clear at this point which pieces of the behavior here are by design and which are temporary kludges.

Here are some non-normative examples of how it can be used:

The definition (struct-easy (my-data field-one field-two)) behaves the same way as (struct my-data (field-one field-two)) except that it also implements a gen:custom-write behavior based on make-constructor-style-printer.

In the rest section, the first occurrence of the #:other keyword begins passing the rest of the subforms through to the struct form. To illustrate, this means the definition (struct-easy (my-data field-one field-two) #:other ...) behaves the same way as (struct my-data (field-one field-two) ...), aside from that custom write behavior.

If #:equal appears in the rest section (before any occurrence of #:other), then an implementation of gen:equal+hash will be generated so that the resulting structure type’s instances are equal? if their corresponding fields are equal?.

If a list of the form (#:guard-easy body-expr ...) appears in the rest section (before any occurrence of #:other), then the resulting structure type runs each body-expr when an instance is constructed. In those expressions, local variables corresponding to each field (e.g. field-one and field-two) are bound to the values that instance is being constructed with. The results of each body-expr are ignored; they’re expected to raise exceptions if the field values are unacceptable.

If #:write followed by an expression appears, then whenever an instance of this structure type would have its gen:custom-write behavior invoked, it runs that expression with local variables in scope corresponding to each field name (e.g. field-one and field-two), each bound to the instance’s respective field value. The expression is expected to return a procedure that takes the instance value itself and returns a list of values to be printed. (Since these are redundant, typically the expression will either ignore the field variables or ignore the instance value.) The resulting list of values is printed according to the behavior of make-constructor-style-printer.

If #:error-message-phrase followed by an expression appears, then that expression will be evaluated as the structure type is defined, and its result will be used in certain error messages of the generated gen:custom-write and gen:equal+hash implementations, particularly the errors that occur when these methods are called with a value that isn’t actually an instance of this structure type. The errors are of the form "expected <this> to be <phrase>", and the default "<phrase>" is "an instance of the <name> structure type". For certain structure types, it may make sense to use #:error-message-phrase to change this to a more pithy phrase, like "an RGB color" rather than "an instance of the rgb-color structure type." (However, is it even possible for clients of struct-easy to observe these errors in their programs? This feature might not make any visible difference.)

syntax

(struct-predicate struct-name-id)

Expands to the predicate identifier associated with the given structure type name.

For instance, if a struct is defined as (struct my-data (field-one field-two)), then traditionally we can recognize instances of the struct using (my-data? x), and now we can also recognize them using ((struct-predicate my-data) x).

This comes in handy mostly when defining other syntax transformers that deal with structure type names. Sometimes it allows those syntax transformers to be written using simple syntax templates, saving the trouble of making manual calls to syntax-local-value and extract-struct-info.

syntax

(struct-accessor-by-name struct-name-id field-name-id)

Expands to the struct field accessor identifier associated with the given structure type name and field name.

For instance, if a struct is defined as (struct my-data (field-one field-two)), then traditionally we can extract the first field using (my-data-field-one x), and now we can also extract it using ((struct-accessor-by-name my-data field-one) x).

This comes in handy mostly when defining other syntax transformers that deal with structure type names. Sometimes it allows those syntax transformers to be written using simple syntax templates, saving the trouble of making manual calls to syntax-local-value and extract-struct-info.

syntax

(istruct/c name-id field/c-expr ...)

 
  field/c-expr : contract?
Returns a contract that recognizes an instance of structure type name-id where the fields abide by the respective field/c-expr contracts.

Unlike struct/c (but like match/c), this works even when name-id is an immutable structure type name and the field/c-expr contracts contain one or more impersonator contracts.

However, this comes at the price of some quirks. This operation works by reconstructing the struct altogether when a higher-order projection is taken. This means the projection of this struct isn’t necessarily eq?, equal?, or impersonator-of? to the original value. In fact, the projection becomes an instance of the structure type name-id, even when the original value is an instance of a distinct structure subtype of name-id.

syntax

(define-imitation-simple-struct
  (inst?-id inst-field-id ...)
  inst-id
  reflection-name-expr inspector-expr option ...)
 
option = (#:prop prop-expr prop-val-expr)
  | (#:gen gen:name method-definition ...)
  | (auto-write)
  | (auto-equal)
 
  inspector-expr : (or/c inspector? #f 'prefab)
  reflection-name-expr : symbol?
  prop-expr : struct-type-property?
Creates a new structure type, and defines struct-like operations which construct and deconstruct an actual structure value of that type.

The variable inst?-id is defined to be a predicate that detects these structures.

Each variable inst-field-id is defined to be a procedure that accesses the corresponding positional field of any given one of these structures.

The variable inst-id is defined to be a match expander that can be used to construct or match these structures.

The structure type is created with the given reflection name, the given inspector, a number of fields equal to the number of inst-field-id variables specified, and structure type property bindings determined by the given option entries.

The reflection name is used for certain reflective operations.

If the inspector is the value 'prefab, this accesses a prefab structure type instead of creating a new one, and there must be no option entries specified.

An option of the form (#:prop prop-expr prop-val-expr) is like providing the struct option #:property prop-expr prop-val-expr. It specifies a binding of the prop-expr structure type property to the value prop-val-expr.

An option of the form (#:gen gen:name method-definition ...) is like providing the struct option #:methods gen:name [method-definition ...]. The gen:name must be an identifier with a transformer binding that specifies a generic interface as defined by define-generics. The method-definition forms define the implementation of this interface’s methods for this new structure type just as they do in struct.

An option of the form (auto-write) specifies implementations of gen:custom-write and prop:custom-print-quotable which use make-constructor-style-printer to display the value.

An option of the form (auto-equal) specifies an implementation of gen:equal+hash which treats any two of these structures as equal? if their fields are equal?.

Note that unlike struct, this has no support for creating structure types that have supertypes, subtypes, guard procedures, mutable fields, or automatic fields. For the most part, all these features except supertypes and subtypes can be simulated: Mutable fields can be simulated with immutable fields that contain mutable boxes, while guard procedures and automatic fields can be simulated by defining another procedure to call instead of calling the defined constructor directly.

structure type property expander

(auto-write)

A syntax which is only useful as an option to define-imitation-simple-struct. In that context, it specifies that the created structure type should have implementations of gen:custom-write and prop:custom-print-quotable which use make-constructor-style-printer to display a structure value.

structure type property expander

(auto-equal)

A syntax which is only useful as an option to define-imitation-simple-struct. In that context, it specifies that the created structure type should have an implementation of gen:equal+hash which treats any two of the structure values as equal? if their fields are equal?.

syntax

(define-imitation-simple-generics
  inst?-id inst-impl?-id
  (#:method method-id () ... (#:this) () ...)
  ...
  prop:inst-id build-inst-impl-id
  prop-name-expr inst-impl-reflection-name-expr supers-expr)
 
  prop-name-expr : symbol?
  inst-impl-reflection-name-expr : symbol?
  supers-expr : (listof (cons/c struct-type-property? (-> inst-impl? any/c)))
Creates a new structure type property, creates a new structure type for use as the type of implementations of that property, and defines generic-interface-like operations for using these things.

The variable inst?-id is defined to be a predicate which determines whether the given value implements the property. Usually a value is considered to implement the property if its structure type has an implementation associated with the property. A structure type descriptor is special; it’s considered to implement the property if the structure type it’s a descriptor for has an implementation associated with the property.

The variable inst-impl?-id is defined to be a predicate which recognizes values of the structure type created for representing implementations of this property.

Each variable method-id is defined to be a procedure of the indicated number of arguments. Each () occurrence represents one argument, and the occurrence of (#:this) represents one argument which receives special treatment. The argument in the (#:this) position is expected to be a value which implements the property. Its implementation for the property determines the rest of the procedure’s behavior.

The variable prop:inst-id is defined to a structure type property descriptor. When a structure type has an implementation associated with the descriptor prop:inst-id, that implementation is also associated with the property the rest of these operations interact with. (In fact, for now this is the descriptor of the property itself.)

The variable build-inst-impl-id is defined to be a procedure which constructs a value which represents an implementation of this property from N arguments, where N is the number of #:method clauses in this definition. Each argument must be an implementation to use for the corresponding method.

The symbol resulting from prop-name-expr is used for certain reflective operations on the structure type property descriptor.

The symbol resulting from inst-impl-reflection-name-expr is used for certain reflective operations on instances of the structure type of implementations.

The list resulting from supers-expr has the same meaning as the supers argument to make-struct-type-property. It’s an association list mapping a structure type property to a function which transform the implementation of this property into the implementation of that one. When a structure type has an implementation of this property, it has an implementation of all of those as well, generated by the given functions.

When composing a supers argument to pass in, keep in mind that the values representing the implementation of this property are very opaque. Usually the only way to use them is by incorporating them into the creation of another structure type. Most supers arguments will simply use constant values or call the generic methods, ignoring the implementation value they receive altogether.

Note that unlike define-generics, this has no support for creating generic interface information that can be used with the struct #:method syntax; it has no support for giving methods optional arguments, keyword arguments, rest arguments, or non-dispatching fast paths; it has no support for letting instances implement some methods but not others; and it does not define a contract combinator.

Some of these drawbacks are tricky to work around, but not all of them. Methods which take complex argument lists or which have fast paths can be defined as wrapper procedures. The idea of an unimplemented method can be approximated by adding a second method that returns a boolean saying whether the first is implemented.

The #:method syntax is not especially feasible. It’s possible to use it by writing a separate define-generics interface with a #:derive-property option that populates this property. However, users should be discouraged from calling that wrapper interface’s methods directly or using its contract combinator, since those things will look at the define-generics interface, not this property it’s intended to populate. Instead of using the #:method syntax, it’s recommended to define various procedures and macros that make calls to build-inst-impl-id with the desired combinations of functionality.

Even the contract combinator can be simulated in a more big-picture sense: Higher-order contracts for structure type properties will tend to need to replace a property’s implementation with an instrumented version. Not all structure types that implement a properly will do so in a way that’s easily decoupled from their other functionality, much less swapped out for another whole implementation. That means the ability to swap something out is essentially another method that structure type can implement, and it can be designed as such.

When writing contracts for values whose design isn’t easy to change, another approach is to intercept those values at the boundaries of a program and convert them into a custom structure type whose design is easier to control.

9 Utilities for Match Expanders

 (require lathe-comforts/match)
  package: lathe-comforts-lib

syntax

(define-match-expander-from-match-and-make
  new-name-id match-name-id make-id-name-id make-list-name-id)
Defines new-name-id as a syntax that expands into a call to match-name-id if it’s used a match expander, make-id-name-id if it’s used as an standalone identifier in an expression context, and make-list-name-id if it’s used at the beginning of a list in an expression context.

syntax

(define-match-expander-attenuated
  new-name-id old-name-id [arg-id arg/c-expr] ... guard-expr)
 
  arg/c-expr : contract?
Defines new-name-id as a syntax that acts as a match expander which takes one subform for each arg-id and expands into a pattern using old-name-id with the same number of subforms.

When the syntax defined this way used as an expression syntax with one subexpression for each arg/c-expr, it first executes each subexpression, projects them each through the corresponding arg/c-expr contract, and evaluates guard-expr with arg-id bound to those projections. If the result of guard-expr is #f, a precondition representing to this guard fails, raising an exn:fail:contract exception. Otherwise, this returns the result of (old-name-id arg-id ...).

When the syntax defined this way is used as an identifier, it returns a function that performs the expression syntax behavior when it’s called.

When the syntax defined this way is used as a match expander with one pattern for each arg-id, it first tries to match the same arguments according to (old-name-id arg-id ...), and it fails if that pattern fails. Then it tries to check each of the arg/c-expr contracts’ contract-first-order-passes? behavior against the respective argument value, and it fails if any of those fails. Then it projects the arguments through those contracts, and it attempts to check the guard-expr with those projections, failing if the guard-expr returns #f. If it hasn’t failed yet, it proceeds to match those projections according to the patterns given at the call site.

syntax

(match/c name-id arg/c-expr ...)

 
  arg/c-expr : contract?
Returns a contract that recognizes a value if it matches a pattern of the form (name-id arg-id ...), where each arg-id is an identifier, and only as long as each value bound to a arg-id this way abides by the respective arg/c-expr contract.

The value’s projection is computed by taking the projections of each of the arguments and then executing (name-id arg-id ...), where this time each arg-id is an identifier already bound to the argument’s projection value. For some match patterns, this may cause substantial changes to the value when projected by this contract: If name-id is vector, it changes immutable vectors to mutable ones. If name-id is a structure type name, it changes instances of subtypes of name-id into instances of name-id itself.

Unlike struct/c (but like istruct/c), this works even when name-id is an immutable structure type name and the arg/c-expr contracts contain one or more impersonator contracts.