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)