Lathe Comforts
1 Evergreen utilities for binding syntax and pure FP
1.1 Binding syntax utilities
binds
define-simple-normalizing-binder
1.2 Functional programming utilities
1.2.1 Bindings and recursion
pass
w-
fn
w-loop
loopfn
1.2.2 Conditionals
mat
expect
matfns
expectfn
dissect
dissectfn
2 Maybe values
nothing
just
maybe?
maybe/  c
3 Strings
immutable-string?
4 Trivial values
trivial
7.0

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 Binding syntax utilities

      1.2 Functional programming utilities

        1.2.1 Bindings and recursion

        1.2.2 Conditionals

    2 Maybe values

    3 Strings

    4 Trivial values

1 Evergreen utilities for binding syntax and pure FP

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

1.1 Binding syntax utilities

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 Functional programming utilities

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

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.

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

syntax

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

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

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

struct

(struct nothing ())

A maybe value that does not contain a value.

Every two nothing values are equal?.

struct

(struct just (value))

  value : any/c
A maybe value that does contain a value.

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

procedure

(maybe? x)  boolean?

  x : 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)  chaperone-contract?

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

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

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

struct

(struct trivial ())

A trivial value.

Every two trivial values are equal?.