Common Syntactic Sugar

The :std/sugar library provides common syntactic sugar and is used throughout the standard library. Note that this module has no runtime footprint, it only defines macros.

To use the bindings from this module:

(import :std/sugar)

defrule

(defrule (name <pattern> ...) [<condition>] <expansion>)

For the simplest macros that fit with a single expansion rule, defrule provides a short-hand compared to writing a defrules with a single rewrite rule.

Examples:

> (import :std/sugar :std/format)
> (defrule (show var ...) (begin (printf "  ~a = ~s\n" 'var var) ...))
> (define-values (x y z) (values 1 [2 3] "4 5"))
> (show x y z)
  x = 1
  y = (2 3)
  z = "4 5"

try

(try
  body ...
  [<catch-clause> ...]
  [<finally-clause>])

catch-clause:
  (catch predicate => K)          ; K is a continuation function of 1 argument
  (catch (predicate var) expr ...)
  (catch (var) expr ...)
  (catch _ expr ...)

finally-clause:
  (finally expr ...)

Evaluates body with an exception catcher and an unwind finalizer.

Examples:

> (import :std/error)
> (def (global-symbol-bound? sym)
    (try (eval sym) #t
      (catch (unbound-global-exception? e) #f)))
> (global-symbol-bound? 'list)
#t
> (global-symbol-bound? 'this-symbol-is-unbound)
#f

> (def depth 0)
> (def (in-ctx f)
    (try
      (set! depth (1+ depth))
      (f)
      (finally (set! depth (1- depth)))))
> depth
0
> (in-ctx (cut displayln depth))
1
> (in-ctx (cut error "foo"))
ERROR "foo" ...
> depth
0

with-destroy

(with-destroy obj body ...)

Evaluates body with an unwind finalizer that invokes {destroy obj}.

> (defclass A (x) transparent: #t constructor: :init!)
> (defmethod {:init! A} (lambda (self) (class-instance-init! self x: 'open)))
> (defmethod {destroy A} (lambda (self) (set! (@ self x) 'closed)))
> (let (a (A)) [(with-destroy a (A-x a)) (A-x a)])
(open closed)
> (def b (A))
> (with-destroy b (error "FOO"))
ERROR: FOO ...
> (A-x b)
closed

defmethod/alias

(defmethod/alias {method (alias ...) type}
  body ...)

Defines a method with one or more binding aliases

Examples:

> (defclass A ())
> (defmethod/alias {foo (bar baz) A} (lambda (self) "foo"))
> [{foo (A)} {bar (A)} {baz (A)}]
("foo" "foo" "foo")

using-method

(using-method obj <method-spec> ...)
=> (begin (using-method obj <method-spec>) ...)

(using-method obj method-id)
(using-method obj (method-id method-name))
=> (def method-id (checked-bound-method-ref o 'method-name))

Defines local procedures for bound methods of an object. This is very useful for avoiding method dispatch if methods of an object are used multiple times within the lexical scope.

Examples:

> (defclass A (x) transparent: #t)
> (defmethod {foo A} (lambda (self) (+ 10 (@ self x))))
> (def a (A x: 13))
> (using-method a foo)
> (foo)
23
> (using-method a (bar foo))
> (bar)
23

with-methods with-class-methods with-class-method

(with-methods obj <method-spec> ...)
=> (begin
     (def klass (object-type obj))
     (with-class-methods klass <method-spec> ...))

(with-class-methods klass <method-spec> ...)
=> (begin
     (with-class-method klass <method-spec>) ...)

(with-class-method klass <method-spec>)
=> (def method-id (or (find-method klass 'method-name) (error ...))) ...)

method-spec:
  method-name             ; method-id is the identifier to resolve and bind
  (method-id method-name) ; method-id is the identifier to bind, resolving method-name

Defines local procedures for methods of an object (class). This is very useful to avoid method dispatch and implicit allocation from method application if the methods of an object (class) are used multiple times within the lexical scope.

The difference from using is that methods are not bound to an object, and you thus have to pass the receiver as first argument to the method. The advantage over using is that there is no implicit allocation for collecting arguments to apply the bound closure of the method.

Examples:

> (defclass C (c) transparent: #t)
> (defmethod {foo C} (lambda (self) (+ 10 (@ self c))))
> (defmethod {frob C} (lambda (self (increment 1)) (pre-increment! (@ self c) increment)))
> (def c (C c: 10))
> (with-methods c foo (frobnicate frob))
> (foo c)
20
> (frobnicate c)
11
> (frobnicate c)
12
> (with-class-methods C::t (fuzz foo) frob)
> (fuzz c)
22
> (frob c)
13
> (with-class-method C::t (baz foo))
> (baz c)
23

while

(while test body ...)

Evaluates body in a loop while the test expression evaluates to true.

Examples:

> (import :std/misc/number)
> (def vector-ref-set! vector-set!)
> (def a #(1 2 3 4 5 6))
> (def i 5)
> (while (<= 0 i)
    (increment! (vector-ref a i))
     (decrement! i))
> a
#(2 3 4 5 6 7))

until

(until test body ...)

Evaluates body in a loop until the test expression evaluates to true.

Examples:

> (import :std/misc/number)
> (def vector-ref-set! vector-set!)
> (def a #(2 3 4 5 6 7))
> (def i 0)
> (until (= i (vector-length a))
    (increment! (vector-ref a i))
    (increment! i))
> a
#(3 4 5 6 7 8)

hash

(hash (key val) ...)

Construct a hash table; the keys are quasiquoted while the values are evaluated.

Examples:

> (import :std/sort :std/misc/symbol)
> (def key 'aaa)
> (def t (hash (a 1) (,key 2) (k (+ 10 13))))
> (hash->list/sort t symbol<?)
((a . 1) (aaa . 2) (k . 23))

hash-eq

(hash-eq (key val) ...)

Like hash, but constructs hash-eq table.

hash-eqv

(hash-eqv (key val) ...)

Like hash, but constructs hash-eqv table.

let-hash

(let-hash hash body ...)

Evaluates the body within a scope where identifier references starting with a . resolve as hash references.

More specifically, the macro rebinds %%ref so that identifiers starting with a . are resolved with the following rules:

  • .x -> (hash-ref hash 'x) ; strong accessor
  • .?x -> (hash-get hash 'x) ; weak accessor
  • ..x -> (%%ref .x) ; escape

Examples:

> (def .c 4)
> (def h (hash (a 1) (b 2) (c 3)))
> (let-hash h [.a .?b ..c .?d])
(1 2 4 #f)

awhen

(awhen (id test) body ...)

Anaphoric when. Evaluates and binds test to id. Evaluates body ... if test is not #f. Otherwise, returns #!void.

Examples:

> (import :std/text/basic-parsers)
> (def (foo c) (awhen (v (char-ascii-digit c)) (* v v)))
> (foo #\3)
9
> (foo #\a)

chain

(chain expression ...)

<expression>:
  proc                        ; unary procedure
  (proc arg* ...)             ; must contain exactly one <> symbol
  (var (proc arg1 arg* ...))  ; var supports destructuring

(chain <> (expression) ...)
=> (lambda (var) (chain var (expression) ...))

chain rewrites passed expressions by passing the previous expression into the position of the <> diamond symbol. In case a previous expression should be used in a sub-expression, or multiple times, the expression can be prefixed with a variable (supports destructuring).

When the first expression is a <>, chain will return a unary lambda.

Examples:

> (chain "stressed"
    string->list
    reverse
    list->string
    (string-append "then have some " <>))
"then have some desserts"

> (chain (random-integer 10)
    (n (if (> n 5) n 0)))
7

> (def foobar
    (chain <>
      ([_ . rest] (map number->string rest))
      (string-join <> ", ")
      (string-append <> " :)")))

> (foobar [0 1 2])
"1, 2 :)"

is

(is [proc] v-or-pred [test: equal?]) -> procedure
(is v [test: equal?])                -> procedure

  proc      := optional unary procedure returning one value
  v-or-pred := if the first argument is a proc, the second one can be a predicate
  test      := optional test procedure, defaults to equal?

is converts a given value into a predicate testing for the presence of the given value. Optionally a transforming procedure can prefix the value, which can in this case also be a procedure. This allows to 'get' a value out of a compound data structure before comparison (first map, then test). For numbers, char and string specialized procedures are used automatically if passed to the macro as value and not as variable. Alternatively, the test: keyword can be used to supply a test, the default is equal?.

Examples:

> ((is "a") "a")
#t

> (def alist '((a . 2) (b . 5) (c . 6)))
> (find (is cdr 5) alist)
(b . 5)

> (filter (is file-type 'directory) (directory-files))
("Documents" "Pictures" "Videos" "Music")

defrule

(defrule (name <pattern> ...) [<condition>] <expansion>)

For the simplest macros that fit with a single expansion rule, defrule provides a short-hand compared to writing a defrules with a single rewrite rule.

Examples:

> (import :std/sugar :std/format)
> (defrule (show var ...) (begin (printf "  ~a = ~s\n" 'var var) ...))
> (define-values (x y z) (values 1 [2 3] "4 5"))
> (show x y z)
  x = 1
  y = (2 3)
  z = "4 5"

defsyntax/unhygienic

(defsyntax/unhygienic (m-id stx) body ...)
(defsyntax/unhygienic m-id f-expr)

defsyntax/unhygienic is a variant of defsyntax, with similar syntax, that allows you to define a macro m-id that is bound to a tweaked variant of the function f-expr or (lambda (stx) body ...) such that it can introduce identifiers in the context in which it is invoked.

[Faré: I admit the precise semantics of defsyntax/unhygienic is beyond my understanding of how hygienic macros works. Hopefully, I can get an explanation from the author Alex Knauth, or from another Racket wizard or macro guru.]

Examples:

> (def aa 22)
> (defsyntax/unhygienic (double-id ctx)
    (syntax-case ctx () ((_ x) (stx-identifier #'ctx #'x #'x))))
> (double-id a)
22

with-id, with-id/expr

(with-id [ctx] (<id-spec> ...) body body+ ...)
(with-id/expr [ctx] (<id-spec> ...) body body+ ...)

with-id and with-id/expr are macros that allow you to unhygienically generate one or many identifiers in the explicitly or implicitly given lexical context ctx, such that the body body+ ... of the macro can then define or refer to these identifiers so they are visible in the rest of the context ctx.

The general form of an identifier specification is (id str1 str2 ...) where id is the identifier to be referenced in the body body+ ... of the macro, and str1 str2 ... are expressions that may evaluate to strings, symbols, identifiers, etc., that will be converted to strings, concatenated, interned in a symbol then associated with the lexical context ctx, as per stx-identifier from the gerbil prelude, such that mentions of id in the body are expanded to mentions of the computed identifier in the target context.

A simplified case of identifier specification is id or (id) which is the same as (id 'id) wherein the identifier stands for itself to be used in the body as refering to the same-named identifier associated to ctx.

If the lexical context ctx isn't explicitly provided as an identifier, the context where with-id itself appears is used; this implicit lexical context is fine for simple direct uses of with-id in the lexical context where the identifiers are to be looked up or defined. However, for more advanced uses where with-id is itself used as a helper deep within a macro that wants to use or define computed identifiers, you will need to explicitly give it the target context in which to compute identifiers: whichever outermost macro is directly invoked in the target lexical context must capture that environment and pass it down every intermediate macro all the way to with-id. These macros may possess a form where the context argument is implicit, but must possess a form where it is explicit.

Now, with-id side-effects the environment to define the identifiers, and repeated uses of with-id with the same context and the same identifiers can cause weird clashes with unhelpful error messages saying Syntax Error: Bad syntax; illegal expression. For this reason, use with-id/expr for read accesses to identifiers: it creates a new scope every time, so you cannot create new visible definitions but also will not cause clashes with unhelpful error messages.

Examples:

;; Defining "variables" A, B, C, D to actually be
;; accessors for positions in a vector.
> (def mem (make-vector 5 0))
> (defrule (defvar name n)
    (with-id name ((@ #'name "@")
                   (get #'name)
                   (set #'name "-set!"))
      (begin
        (def @ n)
        (def (get) (vector-ref mem @))
        (def (set x) (vector-set! mem @ x)))))
> (begin (defvar A 0) (defvar B 1) (defvar C 2) (defvar D 3))
> (begin (A-set! 42)
         (B-set! (+ (A) 27))
         (increment! (C) 5)
         (D-set! (post-increment! (C) 18)))
> mem
#(42 69 23 5 0)
> C@
2

;;; Using with-id to refer to a computed name
> (defrule (var-index var) (with-id/expr var ((@ #'var '@)) @))
> [(var-index A) (var-index B) (var-index C) (var-index D)]
(0 1 2 3)
> (defrule (vars-index var ...) (list (var-index var) ...))
> (vars-index A B C D)
(0 1 2 3)

> (defrule (bad-var-index var) (with-id var ((@ #'var '@)) @))
> [(bad-var-index A) (bad-var-index B) (var-index C) (var-index D)]
*** ERROR IN ...
--- Syntax Error: Bad syntax; illegal expression
... form:   ... detail: (%#define-syntax m (compose syntax-local-introduce (lambda (stx2) (with-syntax ((@ (stx-identifier (stx-car (stx-cdr stx2)) (list (syntax A) '@)))) (syntax (... @)))) syntax-local-introduce))

if-let

(if-let ((id test) ...) then else)
(if-let (id test) then else)

Variant of if that sequentially evaluates each test and if it passes (returns a true value, anything but #f) binds the identifier id to it, that can be then seen by all subsequent tests and by the then clause that is evaluated if all tests pass. However, if any test fails (returns #f), then the else clause is evaluated, which does not see the identifiers.

A single (id test) binding can be done with one fewer levels of parentheses.

The else clause is mandatory. Use when-let instead for a variant where the else clause is always (void).

NB: This if-let binds identifiers sequentially like let*, short-circuits like and, and does not bind any of the identifiers in the else clause, each of which design choices is opposite to the one made in the if-let offered in Common Lisp by Alexandria and UIOP.

Examples:

> (import :std/text/basic-parsers)
> (def (foo a b c)
    (if-let ((x (char-ascii-digit a))
             (y (char-ascii-digit b))
             (z (char-ascii-digit c)))
      (+ x y z)
      -1))
> (foo #\1 #\2 #\3)
6
> (foo #\1 #\A #\3)
-1

when-let

(when-let bindings body ...)

Generalization of awhen where multiple bindings are allowed, and specialization of if-let where the else clause is (void).

Examples:

> (import :std/iter :std/misc/list-builder)
> (def h (hash (1 "foo") (3 "bar") (4 "baz")))
> (with-list-builder (collect)
    (for (n (in-range 1000))
      (when-let ((p (power-of-5 n))
                 (x (hash-get h p)))
        (collect [n x]))))
((5 "foo") (125 "bar") (625 "baz"))

defcheck-argument-type

(defcheck-argument-type <type>...)

For each specified <type>, define a macro check-argument-<type> that checks that each of its argument is of the given <type> or else raises a ContractViolation as per check-argument.

Examples:

> (import :std/number :std/iter)
> (defcheck-argument-type integer vector)
> (def (foo v n start (end #f))
    (check-argument-vector v)
    (unless end (set! end (vector-length v)))
    (check-argument-integer n start end)
    (for (i (in-range start end))
      (increment! (vector-ref v i) n)))
> (def v #(1 2 3 4 5 6))
> (foo v 10 2)
> v
#(1 2 13 14 15 16)
> (foo '(1 2 3) 1 0)
*** ERROR IN ... [ContractViolation]: contract violation
--- irritants: v (vector? v) "bad argument; expected vector" (1 2 3)
> (foo #(1 2 3) 1 "0")
*** ERROR IN ... [ContractViolation]: contract violation
--- irritants: start (integer? start) "bad argument; expected integer" "0"

syntax-eval stx

(syntax-eval expression)

Evaluate the expression during syntax-expansion, and use the result as source.

Examples:

;; Low-level language users like to show off their "fast" O(n) fibonacci.
;; Here is a faster O(1) implementation that supports numbers they don't.
> (import (for-syntax :std/misc/list-builder))
> (def (constant-time-fibonacci n)
    (def precomputed (syntax-eval (list->vector
      (with-list-builder (collect)
        (let loop ((a 0) (b 1))
          (when (<= (integer-length a) 128)
            (collect a) (loop b (+ a b))))))))
    (vector-ref precomputed n))
> (constant-time-fibonacci 186)
332825110087067562321196029789634457848

> (def aa 1)
> (syntax-eval (string->symbol "aa"))
1

syntax-call

(syntax-call expression)
(syntax-call expression ctx . args)

During syntax-expansion, evaluate the expression and apply the resulting function to the context identifier ctx and the arguments. If ctx is not provided, use the syntax-call identifier from the call itself.

The expression can only use functionality imported for-syntax. If may inspect the source location using AST-source and visit ancillary source files, etc.

Examples:

> (import (for-syntax :std/stxutil))
> (def bar 23)
> (def foofoo 42)
> (syntax-call (lambda (ctx . args) (stx-identifier ctx args args)) bar foo)
42

defsyntax-call

(defsyntax-call (macro ctx formals ...) body ...)

Define a macro that takes a lexical context ctx and other argument formals ... and expands into the body .... The context ctx is taken as first argument of the macro invocation, or, if the formals have a fixed size and one argument is otherwise missing, from the macro invocation itself.

Examples:

;;; This is how this-source-file is defined in :std/source
> (begin-syntax
    (def (stx-source-file stx)
      (alet (loc (stx-source stx))
        (vector-ref loc 0))))
> (defsyntax-call (this-source-file ctx)
    (stx-source-file ctx))

;;; Now in some script, you can use:
(import :std/source)
(def $0 (this-source-file))