On this page:
7.1 Hyperbracketed Quotation Operators
taffy-quote
taffy-quote-syntax
7.2 Hyperbracketed Binding Operators
taffy-let
list-taffy-map
list-taffy-bind
8.12

7 Hyperbracketed Operations🔗ℹ

    7.1 Hyperbracketed Quotation Operators

    7.2 Hyperbracketed Binding Operators

7.1 Hyperbracketed Quotation Operators🔗ℹ

 (require punctaffy/quote) package: punctaffy-lib

Quasiquotation is perhaps the most widespread example of an operation with a subterm of the shape that’s known in Punctaffy as a "degree-2 hypersnippet." It may not be the absolute best example of hypersnippet syntax, since quotation is a complex problem domain with additional concerns around escape sequences and round-tripping, but it is the example that motivates the Punctaffy library.

The punctaffy/quote module is for variations of Racket’s own quotation forms that have been redesigned to use Punctaffy’s hyperbrackets for their syntax. This allows them to gracefully support nesting, as in the quotation of code that is itself performing quotation, without requiring that code to be modified with escape sequences. Furthermore, since each of these quotation operators will use the same hyperbracket syntax to represent its nesting structure, they can each gracefully nest within each other.

This design leads to a more consistent experience than the current situation in Racket: At the time of writing, Racket’s quasiquote and quasisyntax can accommodate nested occurrences of themselves but not of each other. Racket’s quasisyntax/loc can accommodate nested occurrences of quasisyntax but not of itself.

For instance, list-taffy-map can accommodate nested occurrences of taffy-quote, as demonstrated in Introduction to Punctaffy.

syntax

(taffy-quote (^< content-and-splices))

 
content-and-splices = atom
  | ()
  | (content-and-splices . content-and-splices)
  | #&content-and-splices
  | #(content-and-splices ...)
  | #s(prefab-key-datum content-and-splices ...)
  | (^<d degree deeper-content-and-splices ...)
  | (^> spliced-list-expr ...)
 
  spliced-list-expr : list?
A variant of quote or quasiquote that uses hyperbrackets to delimit a quoted degree-2 hypersnippet of datum values. Expressions can be supplied within the degree-1 holes of this hypersnippet to cause their resulting lists to be spliced into the surrounding datum content.

Specifically, the holes behave like unquote-splicing. It’s possible to achieve the behavior of unquote by wrapping the expression in a call to list.

The content-and-splices is converted to a list of datum values as follows:

atom

Produces a single datum: Itself.

The atom value must be an instance of one of a specific list of types. Generally, we intend to support exactly those values which are equal? to some immutable value that can appear in Racket code. Some of these values can accommodate internal s-expressions, including spliced expressions, and they’re covered by the other cases of this grammar (list?, box?, pair?, vector?, and instances of immutable prefab structure types). The atom case is a catch-all for those values which are unlikely to ever accommodate internal s-expressions.

Values supported:

  • This operation supports quoting boolean?, char?, keyword?, number?, and extflonum? values. These are immutable values with reader syntaxes, so they fit the description exactly.

  • This operation supports quoting string? values. If the value is a mutable string, it is converted to its immutable equivalent.

  • This operation supports quoting interned symbol? values that aren’t bound to hyperbracket notation in the sense of prop:taffy-notation. This exclusion of hyperbracket notation ensures that such identifiers can be given special-case meanings in the future without breaking code that expected them to be processed like non-hyperbracket syntax. Note that some uses of hyperbracket notation may appear not to be excluded the same way as others, and that’s because as long as nested hyperbrackets are properly matched up, the way this operation processes them tends to have results that explicitly simulate what would have happened if the same identifiers hadn’t been bound to hyperbracket notation in the first place.

  • This operation supports quoting uninterned and unreadable symbols, just like it supports interned symbols. (The same exclusion of hyperbracket notation applies.) Unlike the other values supported here, uninterned and unreadable symbols don’t have a reader syntax and hence don’t appear in textual Racket code, but they do oftentimes appear in macro-generated and dynamically generated Racket code.

Notable exclusions:

  • Out of caution, this operation does not yet support quoting hash? values. There are several places where the design of this support could go wrong: If interpolated expressions were allowed in hashes, their evaluation order would be hard to make guarantees about. If they were allowed in hash keys, expressions that were syntactically identical might end up counting as duplicate keys at read time or expansion time even if their run-time results would have been distinct. As for allowing interpolated expressions to appear in hash entries’ associated values, there isn’t a consistent precedent; Racket’s quasiquote seems to support unquote there, but syntax and quasisyntax leave that location alone, processing neither template variables nor unsyntax. To ensure that we don’t implement a behavior prematurely that turns out to be surprising in practice, we don’t allow hashes to be quoted at all.

  • Out of caution, this operation does not yet support quoting compiled-expression? or regexp? values. These values’ reader syntaxes are complex languages, and it’s easy to conceive of the idea that they may someday be extended in in ways that support internal s-expressions.

  • Out of caution, this operation does not yet support quoting flvector?, fxvector?, or bytes? values. These are mutable values, and it’s possible Racket will someday introduce immutable equivalents that are equal? to them.

()

Produces a single datum: Itself, an empty list.

(content-and-splices . content-and-splices)

Produces a single datum by combining some datum values using list*. The first content-and-splices produces any number of leading arguments for the list* call. The second content-and-splices must produce a single datum, and that datum serves as the final list* argument, namely the tail.

#&content-and-splices

Produces a single datum: An immutable box which contains the datum value produced by the given content-and-splices term. The given content-and-splices term must produce a single datum.

#(content-and-splices ...)

Produces a single datum: An immutable vector which contains all the datum values produced by each of the given content-and-splices terms.

#s(prefab-key-datum content-and-splices ...)

Produces a single datum: A prefab struct which contains all the datum values produced by each of the given content-and-splices terms. The prefab struct’s key is given by prefab-key-datum, which must be a prefab-key? value which specifies no mutable fields.

(^<d degree deeper-content-and-splices ...)

Parses as an opening hyperbracket, and produces datum values which denote a similar opening hyperbracket.

Within the deeper-content-and-splices of an opening hyperbracket like this of some degree N, the same grammar as content-and-splices applies except that occurrences of (^>d degree shallower-content-and-splices ...) for degree less than N instead serve as hyperbrackets that close this opening hyperbracket.

Within the shallower-content-and-splices of a closing hyperbracket of some degree N, the same grammar applies that did at the location of the corresponding opening bracket, except that occurrences of (^>d degree deeper-content-and-splices ...) for degree less than N instead serve as hyperbrackets that close this closing hyperbracket (resuming the body of the opening hyperbracket again).

(TODO: That’s a mouthful. Can we reword this?)

(^> spliced-list-expr ...)

Evaluates the expressions spliced-list-expr ... and produces whatever datum values they return. Each expression must return a list; the elements of the lists, appended together, are the datum values to return. The elements can be any type of value, even types that this operation doesn’t allow in the quoted content.

Each intermediate content-and-splices may result in any number of datum values, but the overall content-and-splices must result in exactly one datum. If it results in some other number of datum values, an error is raised.

Graph structure in the input is not necessarily preserved. If the input contains a reference cycle, this operation will not necessarily finish expanding. This situation may be accommodated better in the future, either by making sure this graph structure is preserved or by producing a more informative error message.

This operation parses hyperbracket notation in its own way. It supports all the individual notations currently exported by Punctaffy (including the ^<d, ^>d, ^<, and ^> notations mentioned here), and it also supports some user-defined operations if they’re defined using prop:taffy-notation-akin-to-^<>d. Other prop:taffy-notation notations are not yet supported but may be supported in the future.

For examples of using taffy-quote, see Introduction to Punctaffy.

syntax

(taffy-quote-syntax maybe-local (^< content-and-splices))

 
maybe-local = 
  | #:local
     
content-and-splices = atom
  | ()
  | (content-and-splices . content-and-splices)
  | #&content-and-splices
  | #(content-and-splices ...)
  | #s(prefab-key-datum content-and-splices ...)
  | (^<d degree deeper-content-and-splices ...)
  | (^> spliced-list-expr ...)
 
  spliced-list-expr : list?
Like taffy-quote, but instead of producing a datum, produces a syntax object.

If the #:local option is not supplied, the scope sets of the quoted content are pruned using the same method as quote-syntax to omit the scope for local bindings that surround the taffy-quote-syntax expression. The only syntax objects in the result that are pruned this way are the ones that correspond to the quoted content; syntax objects that are spliced into the result are left alone.

Note that the result values of spliced expressions must still be non-syntax lists. The syntax->list function may come in handy.

Whereas taffy-quote imitates quote and quasiquote, taffy-quote-syntax imitates quote-syntax.

It may be tempting to compare the splicing support of taffy-quote-syntax to the splicing support of quasisyntax. However, quasisyntax supports template variables and ellispes, and taffy-quote-syntax does not. In the future, Punctaffy may offer a taffy-syntax operation that works more like quasisyntax. For a little more in-depth exploration of what taffy-syntax would hypothetically look like, see Potential Application: Interactions Between unsyntax and Ellipses.

7.2 Hyperbracketed Binding Operators🔗ℹ

 (require punctaffy/let) package: punctaffy-lib

This module uses the higher-dimensional lexical structure afforded by hyperbrackets to define operations that use a kind of higher-dimensional lexical scope.

syntax

(taffy-let ([id val-expr] ...) (^< body-expr-and-splices))

 
body-expr-and-splices = atomic-form
  | ()
  | (body-expr-and-splices . body-expr-and-splices)
  | #&body-expr-and-splices
  | #(body-expr-and-splices ...)
  | #s(prefab-key-datum body-expr-and-splices ...)
  | (^<d degree deeper-body-expr-and-splices ...)
  | (^> spliced-expr)
A variant of let that uses hyperbrackets to delimit a lexical scope in the shape of a degree-2 hypersnippet. Expressions supplied in the degree-1 holes of this hypersnippet behave just as they would normally but without the variable bindings in scope.

The body-expr-and-splices is converted to a syntax object as follows:

atomic-form

Produces itself.

The atomic-form expression must be represented by an instance of one of a specific list of types. Generally, we intend to support exactly those representations which can appear in Racket code. Some of these values can accommodate internal s-expressions, including spliced expressions, and they’re covered by the other cases of this grammar (list?, box?, pair?, vector?, and instances of immutable prefab structure types). The atomic-form case is a catch-all for those values which are unlikely to ever accommodate internal s-expressions.

Values supported:

  • This operation accommodates subforms represented by string?, boolean?, flvector?, fxvector?, char?, bytes?, keyword?, number?, and extflonum? values. These are representations with reader syntaxes, so they fit the description exactly.

  • This operation accommodates subforms represented by interned symbol? values that aren’t bound to hyperbracket notation in the sense of prop:taffy-notation. This exclusion of hyperbracket notation ensures that such identifiers can be given special-case meanings in the future without breaking code that expected them to be processed like non-hyperbracket syntax. Note that some uses of hyperbracket notation may appear not to be excluded the same way as others, and that’s because as long as nested hyperbrackets are properly matched up, the way this operation processes them tends to have results that explicitly simulate what would have happened if the same identifiers hadn’t been bound to hyperbracket notation in the first place.

  • This operation also accommodates subforms represented by uninterned and unreadable symbols, just like it supports interned symbols. (The same exclusion of hyperbracket notation applies.) Unlike the other values supported here, uninterned and unreadable symbols don’t have a reader syntax and hence don’t appear in textual Racket code, but they do oftentimes appear in macro-generated and dynamically generated Racket code.

Notable exclusions:

  • Out of caution, this operation does not yet accommodate subforms represented by hash? values. If interpolated expressions were allowed in hashes, their evaluation order would be hard to make guarantees about. If they were allowed in hash keys, expressions that were syntactically identical might end up counting as duplicate keys at read time or expansion time even if their run-time results would have been distinct. As for allowing interpolated expressions to appear in hash entries’ associated values, there isn’t a consistent precedent; Racket’s quasiquote seems to support unquote there, but syntax and quasisyntax leave that location alone, processing neither template variables nor unsyntax. To ensure that we don’t implement a behavior prematurely that turns out to be surprising in practice, we don’t allow hashes to appear as subforms here.

  • Out of caution, this operation does not yet support quoting compiled-expression? or regexp? values. These values’ reader syntaxes are complex languages, and it’s easy to conceive of the idea that they may someday be extended in in ways that support internal s-expressions. (TODO: Reconsider this.)

(TODO: Currently, we actually let all kinds of representations through, including the ones we’ve listed as being excluded here. Let’s fix this.)

()

Produces itself, a syntax value represented by an empty list.

(body-expr-and-splices . body-expr-and-splices)

Produces a syntax value similar to itself, but with the pair’s head and tail processed recursively.

#&body-expr-and-splices

Produces a syntax value similar to itself, but with the box’s value processed recursively. The box must be immutable.

(TODO: Actually, we don’t enforce immutability yet.)

#(body-expr-and-splices ...)

Produces a syntax value similar to itself, but with the vector’s elements each processed recursively. The vector must be immutable.

(TODO: Actually, we don’t enforce immutability yet.)

#s(prefab-key-datum body-expr-and-splices ...)

Produces a syntax value similar to itself, but with the prefab struct’s field values each processed recursively. The prefab struct must not have any mutable fields.

(TODO: Actually, we don’t enforce immutability yet.)

(^<d degree deeper-body-expr-and-splices ...)

Parses as an opening hyperbracket, and produces a syntax object which denotes a similar opening hyperbracket. The exact way the hyperbracket is re-encoded as syntax is unspecified.

Within the deeper-body-expr-and-splices of an opening hyperbracket like this of some degree N, the same grammar as body-expr-and-splices applies except that occurrences of (^>d degree shallower-body-expr-and-splices ...) for degree less than N instead serve as hyperbrackets that close this opening hyperbracket.

Within the shallower-body-expr-and-splices of a closing hyperbracket of some degree N, the same grammar applies that did at the location of the corresponding opening bracket, except that occurrences of (^>d degree deeper-body-expr-and-splices ...) for degree less than N instead serve as hyperbrackets that close this closing hyperbracket (resuming the body of the opening hyperbracket again).

(TODO: That’s a mouthful. Can we reword this?)

(^> spliced-expr)

Produces an expression which, when evaluated, is equivalent to spliced-expr.

When this syntax object appears in a context where it’s quoted, like as a subform of an expression that’s a quote form, a box, a vector, or a prefab struct, the result is unspecified.

As noted, all boxes, vectors, and prefab structs that are encountered in the body must be immutable. Racket’s reader usually produces immutable boxes and immutable vectors as syntax anyway, and it usually refuses to produce mutable prefab structs as syntax, so the presence of mutability indicates a devoted effort is underway somewhere. If this operation cloned the object to process its elements, the fact that the result was a different mutable object than the original might interfere with whatever that devoted effort was meant to accomplish. Instead, out of caution, the presence of a mutable box, vector, or prefab struct is currently treated as an error.

Graph structure in the input is not necessarily preserved. If the input contains a reference cycle, this operation will not necessarily finish expanding. This situation may be accommodated better in the future, either by making sure this graph structure is preserved or by producing a more informative error message.

This operation parses hyperbracket notation in its own way. It supports all the individual notations currently exported by Punctaffy (including the ^<d, ^>d, ^<, and ^> notations mentioned here), and it also supports some user-defined operations if they’re defined using prop:taffy-notation-akin-to-^<>d. Other prop:taffy-notation notations are not yet supported but may be supported in the future.

Examples:
> (pd / let ([x 5])
    (taffy-let ([x (+ 1 2)]) / ^<
      (+ (* 10 x) / ^> x)))

35

> (pd / taffy-let () / ^<
    (if #f
      (^> / error "whoops")
      "whew"))

"whew"

syntax

(list-taffy-map (^< body-expr-and-splices))

 
body-expr-and-splices = atomic-form
  | ()
  | (body-expr-and-splices . body-expr-and-splices)
  | #&body-expr-and-splices
  | #(body-expr-and-splices ...)
  | #s(prefab-key-datum body-expr-and-splices ...)
  | (^<d degree deeper-body-expr-and-splices ...)
  | (^> lst-expr)
 
  lst-expr : list?
A variant of map that uses hyperbrackets to delimit the transformation code in a degree-2 hypersnippet. Expressions supplied in the degree-1 holes of this hypersnippet are evaluated first, and they supply the lists to iterate over. There must be at least one list given to iterate over, and all the lists must be of the same length.

Per map, the result of the body on each iteration must be a single value. The overall result is a list of the body’s results in the order they were generated.

The body hypersnippet is parsed according to the same rules as taffy-let.

Example:
> (pd / list-taffy-map / ^<
    (format "~a, ~a!"
      (^> / list "Hello" "Goodnight")
      (^> / list "world" "everybody")))

'("Hello, world!" "Goodnight, everybody!")

syntax

(list-taffy-bind (^< body-expr-and-splices))

 
body-expr-and-splices = atomic-form
  | ()
  | (body-expr-and-splices . body-expr-and-splices)
  | #&body-expr-and-splices
  | #(body-expr-and-splices ...)
  | #s(prefab-key-datum body-expr-and-splices ...)
  | (^<d degree deeper-body-expr-and-splices ...)
  | (^> lst-expr)
 
  lst-expr : list?
A variant of append-map (named like list-bind from Lathe Comforts) that uses hyperbrackets to delimit the transformation code in a degree-2 hypersnippet. Expressions supplied in the degree-1 holes of this hypersnippet are evaluated first, and they supply the lists to iterate over. There must be at least one list given to iterate over, and all the lists must be of the same length.

Per append-map, the result of the body on each iteration must be a list. The overall result is the concatenation of the body’s list results in the order they were generated.

The body hypersnippet is parsed according to the same rules as taffy-let.

Example:
> (pd / list-taffy-bind / ^<
    (list (^> / list 1 3 5) (^> / list 2 4 6)))

'(1 2 3 4 5 6)