Skip to content

Domain Modeling Language

R. C. Howell edited this page Jun 30, 2022 · 1 revision

Overview

A simple DSL (domain specific language) based on the Ion text format is used to define a type universe. Within a type universe, the developer defines one or more type domains, each of which consists of a number of product, record and sum type definitions. Together, these type definitions describe the complete structure of a tree and comprise all the data used to generate the code listed in Code Generation.

A Note About Terminology

Although PIG currently only generates code for Kotlin, In the future PIG will generate source code for other languages. Thus, we felt we should avoid terms like "enum", "class", "struct", since these all have different meanings unique to each language.

Each type definition consists of a name and zero or more definitions for its elements. The element definition consists of a name and data type.

Product Types

PIG's product types are named after the same from type theory. Conceptually, products are tuples, represented as a class in Kotlin.

Here is a simple example of a tree definition that uses only a single product type.

(define sample_type_domain
    (domain
        (product person 
            first::symbol 
            last::symbol 
            children::(* person 0))
    )
)

This type domain defines a single product type named person with three elements: first, last and a list of at least 0 children.

Element definitions take the following form:

    <property_name>::<data_type>

Where:

  • property_name is the name of the property in the corresponding Kotlin class. For product types, this is required.
  • data_type is the name of the data type of the element. This can be one of the supported primitive types: int, symbol, bool, ion, or any other data type defined in the same domain (excluding sum variants which will be discussed below).

TODO: describe the way it is today, provide a link to: https://github.com/partiql/partiql-ir-generator/issues/98 (possibly this is better moved to after the s-expression representation is described)

Record Types

Here is a simple example of a tree definition that uses only a single record type.

(define sample_type_domain
    (domain
        (record person 
            first_name::(first symbol)
            last_name::(last symbol) 
            (children (* person 0))
    )
)

This record stores the same information as the product person shown above. Aside from the obviously different syntax, the primary difference between product and record types are in their s-expressions representations. More details will be included in this later--for now the reader should know that the names of the elements are included the s-expressions of a record and this is not true for product types.

Field definitions take the following form:

    <property_name>::(<field_name> symbol)

Where:

  • property_name is the name of the property in the generated Kotlin class. Unlike product types, this is optional. When not specified, the property_name defaults to the field_name.
  • field_name is the name of the field in the s-expression representation.

The Kotlin class of this record type isn't shown because it has the same API as the product type.

Also note that Builder interface also includes definitions needed to construct record types as well.

Sum Types

Sum types are used to hold a value that could take on several different, but fixed, types. Only one of the types can be in use at any one time, and the name of the type, known as a tag, explicitly indicates which one is in use. Sum types are also known as algebraic We use the term "variant" to refer to one of the possible types for a given sum.

Sum types are a natural fit for modeling expressions in a programming language. Let's demonstrate this with an AST for a very simple toy calculator language that supports integer literals and simple binary expressions:

(define calculator_ast
    (domain
        (sum operator (plus) (minus) (times) (divide) (modulo))
        (sum expr 
            (lit value::int)
            (binary op::operator left::expr right::expr)
        )
    )
)

This involves two sum types: operator and expr.

The operator sum declares five different arithmetic operations: plus, minus, times, divide, and modulo. Due to the syntax used here (more on that later), each of these is a product type, and each gets a Kotlin class similar to the example shown above (shown below). However, none of these have any elements.

The expr sum defines two possible types of expressions that exist our toy calculator: lit and binary.

The Kotlin equivalent of a sum type is a sealed class.

Record Types as Sum Type Variants

PIG uses the syntax used to define a variant's elements to determine if it is a product or record.

(define toy_ast
    (domain
        (sum expr
            // This syntax defines a `record` variant.  Note the similarity to a non-variant `record`.
            (let
                (name symbol)
                (value expr)
                optional_name::(body expr))
                
            // This syntax defines a `product` variant.  Note the similarity to a non-variant `product`.
            (binary op::operator left::expr right::expr)
        )
        // ... 
    )
)

As with non-variant record elements, specifying the property name is optional and defaults to the field name.

Clone this wiki locally