8.12

5.3 Annotations and Static Information🔗ℹ

Annotations produce static information when used with the :: binding or expression operator. Similar to binding macros, which can either be simple expansions or use lower-level machines, an annotation macro can use lower-level machinery to explicitly produce static information or manipulate static information produced by subannotation forms.

The annot.macro form defines an annotation. In the simplest case, the expansion of an annotation can be another annotation:

annot.macro 'AlsoPosn':

  'Posn'

> Posn(1, 2) :: AlsoPosn

Posn(1, 2)

Note that annot.macro defines only an annotation. To make AlsoPosn also a binding operator, you can use bind.macro:

bind.macro 'AlsoPosn ($x, $y)':

  'Posn($x, $y)'

> def AlsoPosn(x, y) = Posn(1, 2)

> x

1

To define an annotation with explicit control over the associated predicate, use annot_meta.pack_predicate. This implementation if IsPosn creates a new predicate that uses is_a with Posn, so it checks whether something is a Posn instance, but it doesn’t act as a Posn-like binding form or constructor:

annot.macro 'IsPosn':

  annot_meta.pack_predicate('fun (x): x is_a Posn')

> fun get_x(p :: IsPosn):

    Posn.x(p)

> get_x(Posn(1, 2))

1

> get_x(10)

get_x: argument does not satisfy annotation

  argument: 10

  annotation: IsPosn

The annot_meta.pack_predicate takes an optional second argument, which is static information to associate with uses of the annotation. Static information (the second argument to annot_meta.pack_predicate) is a parenthesized sequence of parenthesized two-group elements, where the first group in each element is a key and the second element is a value.

A value for statinfo_meta.dot_provider_key should be a syntax object naming a dot-provider transformer. So, if we want to define a Vector annotation that is another view on Posn where the “fields” are angle and magnitude instead of x and y, we start with an annotation definition that refers to a vector_dot_provider that we will define:

annot.macro 'Vector':

  annot_meta.pack_predicate(

    'fun (x): x is_a Posn',

    '(($statinfo_meta.dot_provider_key,

       vector_dot_provider))'

  )

A dot-provider transformer is defined using dot.macro. A dot-provider transformer always receives three parts, which are the parsed expression to the left of the dot, the dot itself, and an identifier to the right of the dot. The dot provider associated with Vector access angle and magnitude “fields” by calling helper functions:

dot.macro 'vector_dot_provider $left . $right':

  match right

  | 'angle': 'vector_angle($left)'

  | 'magnitude': 'vector_magnitude($left)'

fun vector_angle(Posn(x, y)):

  math.atan(y, x)

fun vector_magnitude(Posn(x, y)):

  math.sqrt(x*x + y*y)

With those pieces in place, a binding using :: Vector creates a dot provider:

def vec :: Vector = Posn(3, 4)

> vec.angle

0.9272952180016122

> vec.magnitude

5

A macro can explicitly associate static information with an expression by using statinfo_meta.wrap:

expr.macro 'or_zero $p $tail ...':

  statinfo_meta.wrap(

    '$p || Posn(0,0)',

    '(($statinfo_meta.dot_provider_key,

       vector_dot_provider))'

  )

> or_zero(Posn(3, 4)).magnitude

Posn(3, 4)

> or_zero(#false).magnitude

Posn(0, 0)

A similar effect could be acheived by expanding to '($p || Posn(0, 0)) :: Vector', but for better or worse, this implementation of or_zero omits an extra predicate on the result of the expression, and instead claims that it will always work as a Vector.

If a name is otherwise bound but has no static information associated with the binding, the statinfo.macro form can associate static information. In the following example, zero is defined without a result annotation, but statinfo.macro is used to associate static information to zero using statinfo_meta.call_result_key. The value for statinfo_meta.call_result_key should be static information itself, so we use statinfo_meta.pack to pack it from a syntax-object representation.

The statinfo_meta.wrap and annot_meta.pack_predicate functions automatically pack for you, because they expect a syntax object that represents static information. The overall right-hand side result for statinfo.macro is similarly automatically packed.

fun zero():

  Posn(0, 0)

statinfo.macro 'zero':

  '(($statinfo_meta.call_result_key,

     $(statinfo_meta.pack(

         '(($statinfo_meta.dot_provider_key,

            vector_dot_provider))'

       ))))'

> zero().magnitude

0

The statinfo.macro form expects '' containing an identifier or operator, not a more function-like pattern, because it’s mean to define a constant association between a name and static information.