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.

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.

with-destroy

(with-destroy obj body ...)

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

defmethod/alias

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

Defines a method with one or more binding aliases

using

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

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.

while

(while test body ...)

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

until

(until test body ...)

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

hash

(hash (key val) ...)

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

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

awhen

(awhen (id test) body ...)

Anaphoric when. Evaluates and binds test to id. Evaluates body ... if test is not #f.

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