On this page:
6.1 Hyperbracketed Quotation Operators
taffy-quote
taffy-quote-syntax
6.2 Hyperbracketed Binding Operators
taffy-let
list-taffy-map
list-taffy-bind
8.2

6 Hyperbracketed Operations

    6.1 Hyperbracketed Quotation Operators

    6.2 Hyperbracketed Binding Operators

6.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 symbol? values as long as they aren’t hyperbracket notation (i.e. identifiers which have transformer bindings that implement prop:hyperbracket-notation). Only interned symbols have a reader syntax, but this operation accepts uninterned and unreadable symbols anyway. Symbols exist to be used in Racket code, so they do all appear there, even if they don’t all appear in textual 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: Hashes have unspecified iteration order (potentially affecting the order splices would be evaluated in), and their keys are unique (potentially unique both after and before processing hyperbrackets and splices). There isn’t much of a precedent, either; Racket’s quasiquote seems to support unquote in a hash entry’s value, but syntax and quasisyntax leave hash entries’ values alone, processing neither template variables nor unsyntax in that location. It’s possible that treating hashes as unquotable values will be the design that raises the fewest questions.

  • 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:hyperbracket-notation-prefix-expander. Other prop:hyperbracket-notation notations are not yet supported but may be supported in the future.

Out of the hyperbracket notations this operation does support, not all of them will necessarily be preserved in the quoted output; some may be replaced with other, equivalent hyperbracket notations. In general, this operation will strive to preserve the notations that were actually used at the call site. Where it fails to do that, it will use notations exported by the punctaffy module (e.g. the ^<d and ^>d notations).

At the moment, there is no particular notation that this operation is committed to preserving, so users should not rely on specific outputs. Users should only use this operation to generate code for informal visualization purposes, code that will be evaluated with the entire punctaffy module in scope, and code that they’re ready to process using a parser that understands all the hyperbracket notations currently exported by the punctaffy module.

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.

6.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 symbol? values as long as they aren’t hyperbracket notation (i.e. identifiers which have transformer bindings that implement prop:hyperbracket-notation). Only interned symbols have a reader syntax, but this operation accepts uninterned and unreadable symbols anyway. Symbols exist to be used in Racket code, so they do all appear there, even if they don’t all appear in textual Racket code.

Notable exclusions:

  • Out of caution, this operation does not yet accommodate subforms represented by hash? values. Hash keys must be unique, both after and before processing hyperbrackets, and this may lead to an unnecessarily confusing design.

  • 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:hyperbracket-notation-prefix-expander. Other prop:hyperbracket-notation notations are not yet supported but may be supported in the future.

Out of the hyperbracket notations this operation does support, not all of them will necessarily be preserved as-is; some may be replaced with other, equivalent hyperbracket notations, which may be detectable using quote. In general, this operation will strive to preserve the notations that were actually used at the call site. Where it fails to do that, it will use notations exported by the punctaffy module (e.g. the ^<d and ^>d notations).

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)