Overview: Type System

Nyarna's type system has been designed around the concept that types carry the content's semantics. For example, if you want to produce output that renders some part of a text in boldface, you would define a record type \Bold with a single content field, and use that in your input.

For a typical document, this implies that input structures are generally literal text, interleaved with record instances. To be able to model this in the type system, Nyarna uses a lattice to define relationships between types. For example, the basic type \Text and a record type \Bold have the supertype \Intersection(\Text, \Bold). Content where you concatenate values of these two types would then have the type \Concat(\Intersection(\Text, \Bold)).

The type system contains some types specific for describing the structure of documents: Besides \Concat types, which describe concatenations, there are \Sequence types that describe a list of paragraphs – or more abstractly put, a sequence of separated items.

Nyarna infers types if possible. For example, you do not need to specify which type a function returns, as that can be calculated automatically. You do need to specify types of record fields and input parameters.

This paragraph contains some literal text
\Bold(and some bold text).

\declare:
  # function return types are usually inferred,
  # even for recursive functions.
  writeNTimes = \func:
    text: \Text
    num : \Natural
  :body:
    \if(\num::gt(0), \text\writeNTimes(
      \text, \num::sub(1)))
  \end(func)
\end(declare)

\writeNTimes(Spam, 42)

With its somewhat peculiar set of types, Nyarna is able to understand operations on concatenation and paragraph structures:

Concatenations will always be automatically flattened – you cannot have a concatenation value inside of a concatenation value. Also, concatenation of text values is an operation which generates a single text value. Thus, a concatenation value will never contain consecutive text values. Finally, a concatenation whose elements are all of type \Text, has the inferred type \Text. \Concat(\Text) is not a valid type.

Similarly, \Sequence values will also be automatically flattened. However, they are able to contain \Concat values, and don't collapse on \Text. There is an implicit conversion defined from \Sequence to \Concat types so that you can use empty lines in places that don't expect a sequence. Any paragraph that has the type \Void will be evaluated, but stripped from the resulting sequence value. This makes it simple to separate declarations like \declare from content.

If you want a data structure that doesn't imply these operations, there's also \List.

The following call \f(x) is part of a
concatentation and surrounded by text.

\block:
  This call to 'block' has two paragraphs,
  its inferred type will be Sequence(Text).

  The call simply returns this content,
  the paragraphs will be flattened.
\end(block)

To process the input, Nyarna needs a way to separate differently typed values. For this, there's \match and \matcher, where the former is a control structure while the latter defines a function. They infer their return type by calculating the intersection of the type of each branch. \matcher also infers the type of its single parameter from the set of given types.

These structures are the core of backend processing: You can walk over an input concatenation, and take action based on which type each item has. Eventually you'll generate a \Text value which can then be the output.

By separating type-based dispatching from actual types, you are able to implement multiple independent backends to generate different output from your input.

This way of processing content has been primarily inspired by XSLT. However, unlike XSLT's path-based selection, Nyarna's type-based approach guarantees type safety.

\declare:
  Bold = \Record:
    content: \Concat(\Content) {primary}
  \end(Record)
  Italic = \Record:
    content: \Concat(\Content) {primary}
  \end(Record)
  Content = \Intersection(\Bold, \Italic, \Text)
  procContent = \matcher:
  :(\Text):|\t|
    \t
  :(\Bold):|\b|
    <b>\map(\b::content, \procContent)</b>
  :(\Italic):|\i|
    <i>\map(\i::content, \procContent)</i>
  \end(matcher)
\end(declare)

\map(func=\procContent):
  As we see \Italic(above), matchers
  \Bold(can be \Italic(used)) recursively
  to process typical structures.
\end(map)