Malloy Blog

annotation and tagging banner

Annotation and Tagging are a pair of features for attaching metadata to objects in a Malloy model. The idea of attaching metadata to fragments of a program is a common one in modern programing languages. When designing this feature for Malloy, we were not certain what uses there might be for metadata, because a Malloy model is a slightly different kind of programming and might have slightly different requirments for its metadata.

Because of this, the main feature of the Malloy design for annotation is that it imposes almost no structure on what kinds of data might be contained in the annotations. Annotations in Malloy are surface which is flexible and powerful, to allow users of Malloy to investigate the possibilities of annotation that we were unable to envision when we designed Malloy.

Malloy uses annotation sparingly, but we are noticing more projects using Malloy and taking advantage of annotations, and now seemed like a good time to go into a little more detail on the feature so that it would be clear how to take advantage of all the power offered by annotations.

Annotations

Annotations can be attached to models, sources, queries, views, and and field calculations. (e.g. dimensions, measures, caclucations, etc.) An annotation is some text starting with the # character and continuing to the end of the line.

Annotation lines beginning with ## are applied to the model. Annotations beginning with a single # are applied to the next object being defined.

## (def model-level-notice "This is a model level annotation")

# "queryProperties": { "notice": "This annotation is attach to the query 'first_row'" }
query: first_row is some_src -> {select: *; limit 1}

You will notice in these examples that each annotation is written in a different language, this is just an example of what is possible. An application can choose to parse its own annotations any way it chooses, or even to not parse them at all and simply use them as free form text. There is a Malloy companion property language (Malloy Tag Language) designed to be used with annotations, but applications are free to interpret their annotations in any way which make sense to them.

Annotation Prefixes

Multiple applications can decorate a Malloy model with annotations, and there is a convention which will allow applications to add annotations even without being aware of each other. This is the "annotation prefix".

When an application requests annotations for an object, a regular expression is passed along with the request, and only annotations matching that regular expression are returned. Here are some example annotation usages with their annotation prefixes:

  • prefix: /^# /

    • Instructions to the Malloy rendering library

    • Example: # chart { type=bar variant=stacked }

    • Parsed by the renderer using the Malloy Tag Language (See the Render Tag Documentaion)

  • prefix: /^##!/

    • Flags to control the Malloy to SQL translator

    • Example: ##! experimental { featureName1 }

    • Parsed by the Malloy translator, alsing using the Malloy Tag Language (See Experimental Features)

  • prefix: /^#"/

    • Doc strings for describing objects

    • Example: #" This source requires a date filter expression (fex)

    • Parsed by: Nothing yet, this is aspirational. Our intention is this would be where developer documentation for reusable components in Malloy models would be written, allowing an IDE to use the annotations to offer information to developers.

  • prefix: /#(yourApp)/

    • Annotations for the app yourApp

    • Example: #(docs) size=medium

    • We use the (docs) prefix internally to attach formatting instructions for the Malloy documentation package.

Indirect Annotations

In addition to annotations directly placed on an object, there are two other ways for an object to have annotation metadata.

If one object is an extension, or refinement of an existing object, it will have all the annotations of the object it is based on, as well as it's own annotations. For example

// this will render as a bar chart
# bar_chart
view: by_color is { group_by: color; aggregate: thing_count is count() }

// will "inherit" the bar_char annotation from by_color
view: by_color_and_size is by_color + { group_by: size }

// will show the by_color bar chart, only for the most common colors
nest: top_colors is by_color + { limit: 5 }

The other indirect way annotations can be placed on an object is available to objects which can be defined with a single statement

// Every measure defined in this measure statement will have the "private" annotation, in addition
// to any annotations on the actual object.
# private
measure:
    # currency=US_DOLLARS
    total_revenue is sum(revenue)
    # font_color=green
    profitable is pick 'Yes' when sum(revenue) > sum(cost) else 'No'

The Malloy Tag Language

In some of the examples above we showed annotations intended be be parsed by an S-Expression parser and a JSON Parser, but Malloy also has its own language for writing simple property-value data structures, the Malloy Tag Language.

More details on the tag language are in the documentation, but here is a quick overview to give a flavor of the language.

Tag Values

  • A tag has a value which can be a string or an array of values

  • A string can be quoted, but if it is a simple series of alphanumeric chartacters it can also be unquoted

  • An array is written using [ ] to bracket the contents (e.g. [mark mack molly mandy])

  • There is no boolean value, while you can use the strings true and false as values, the intention is that true/false conditions are indicated by presence/absence of a tag

go_color=green
stop_color="red"
slow_colors=[yellow orange "yellowish orange"]

// a boolean ..
slowdown
// turning off a boolean
-slowdown

Tag Properties

This is unlike other property languages. A tag, in addition to values, can also have properties.

  • Properties on a tag can be set using the familiar . (dot) gesture

  • {} can be used to specify a number of properties at once

slow_colors=[yellow orange "yellowish orange"]
slow_colors.flashing
slow_colors { blinks_per_minute=10 safetymode }

Sample Code For Annotation Usage

For some example metadata markup which looks like this ...

// some documentation
#(myDoc) This is a lot of documentation
#(myDoc) across many lines
// and some properties
#(myTags) force default=go 
source: foo is ...

Here is some code which might be written to access the metadata on an object from the Malloy API which has annotations.

function myTagsAndDocs(t: Taggable): { tags: Tag, docs: string[] } {
    const tagParse = t.tagParse({prefix: /^#\(myTags\)/});
    if (tagParse.log) {
        // deal with syntax errors, or ignore them
        throw new Error('syntax errors in tag parse');
    }
    return {
        tags: tagParse.tag,
        docs: t.getTaglines(/^#\(myDocs\)/),
    };
}

// ...

const metadata = myTagsAndDocs(someObj)
if (metadata.tags.has('force')) do_force_action();
if (metadata.tags.has('default')) set_default(metadata.tags.text('default'));
setDocs(metadata.docs);

The Future of Annotation

We are waiting to see how annotations are used. We have many ideas for the future, but we have decided on "wait and learn" approach for this feature. Things we have talked about are ...

  • Some syntax for annotation metadata which is longer than one line

  • More formality on "annotation prefixes", perhaps requiring prefixes to match a pattern, or maybe some sort of "prefix registry" to make certain applications do not inadvertantly affect metadata from other applications.

  • More formality on which annotations are tags, and what the "schema" is for a tagged annotation, allowing the IDE to assist in tagging complex objects.