8.16.0.4

8.4 Annotation Low-Level Protocol🔗ℹ

With annotation constructors such as &&, satisfying, and converting, most annotation macros can be implemented by rewriting into existing annotation forms as shown in Binding and Annotation Macros. The annot.macro form also supports a low-level protocol. A macro opts into the low-level protocol by returning a result build with annot_meta.pack_predicate or annot_meta.pack_converter.

A simple predicate annotation can be implemented with just annot_meta.pack_predicate. The following implementation of 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 achieved 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, The example uses def, plus dynamic to ensure that def cannot infer any static information to bind automatically. 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.

def zero:

  dynamic(fun ():

            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.

An annotation macro can create a convert annotation directly using annot_meta.pack_converter. When a macro parses annotations, it can use annot_meta.unpack_converter to handle all forms of annotations, since predicate annotations can be automatically generalized to converter form. A converter annotation will not unpack with annot_meta.unpack_predicate. Use annot_meta.is_predicate and annot_meta.is_converter to detect annotation shapes and specialize transformations.