## Overview TODO ## Type Model For the `person` example, PIG generates code that looks like the example below. This class is a [persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure) and therefore cannot be directly modified. Functions provided to manipulate instances of this class (`withMeta` and `copy`) always preserve the original version and return modified copies. ```kotlin /** The Kotlin implementation of the type domain named `sample_type_domain`. */ class SampleTypeDomain private constructor() { // ... other stuff /** * The Kotlin implementation of the `person` `product` type. */ class Person( val first: SymbolPrimitive, val last: SymbolPrimitive, val children: List, override val metas: MetaContainer ) : SampleTypeDomainNode() { /** Converts person to its Ion s-expression representation. */ override fun toIonElement(): SexpElement { /* ... */ } /** Creates a copy of [Person] node that is identical to the original but includes the specified key and meta. */ override fun withMeta(metaKey: String, metaValue: Any): Person { /* ... */ } /** Creates a copy of [Person], replacing the existing metas collection. */ override fun copy(metas: MetaContainer): Person { /* ... */ } /** * Creates a copy of [Person] with new values for the specified properties. * * Each parameter corresponds to a property on this class. If left unspecified, the copy will have * the same value as the original. */ fun copy( first: SymbolPrimitive = this.first, last: SymbolPrimitive = this.last, children: List = this.children, metas: MetaContainer = this.metas ) { /* ... */ } /** * Determines if this [Person] is equivalent [other]. * * To be equivalent, [other] must be an instance of [Person] and all of its properties (except [metas]) must be * equivalent. * * This is recursive and applies deeply to all child nodes. * */ override fun equals(other: Any?): Boolean { /* ... */ } /** * Computes a hash code for the current instance of [Person]. * * The has code is computed once, the first time this function is invoked and the value is then re-used. * * The [metas] property is not an input into the hash code computation. */ override fun hashCode(): Int { /* ... */ } } // ... other stuff } ``` The first thing the reader will note is that `Person` resides within the `SampleTypeDomain` class which is being used as a namespace. Some projects have many type domains sharing the same class names and this helps avoid ambiguity and namespace pollution. The next thing the reader might notice is that `Person` implements some functionality provided by Kotlin [data classes](https://kotlinlang.org/docs/data-classes.html). Such as `.copy`, `.equals` and `.hashCode`. The `data class` feature of Kotlin can't be used in this case because data classes always include all of their properties in the `.hashCode()` and `.equals()` implementations, and we do not include [metas] in the definition of equivalence for any PIG-generated class. (More on metas later.) ## Builders Although the constructor of `Person` seen in the previous section was public, this is not the preferred way of creating instances. The preferred way involves using `SampleTypeDomain.Builder`. The following code creates a node representing a person and two children: ```Kotlin val person = SampleTypeDomain.build { person( "Billy", "Smith", listOf( person("Jack", "Smith", listOf()), person("Sue", "Smith", listOf()) ) ) } ``` This approach has the benefit of being very clear and concise because it is not necessary to fully qualify each type or to add `import` statements for each the generated each types. This especially true for large projects with many PIG-generated types and type domains. The relevant parts of `SampleTypeDomain` which makes this work: ```kotlin class SampleTypeDomain private constructor() { companion object { fun build(block: Builder.() -> T) { // ... } // ... other stuff } interface Builder { fun person( first: String, last: String, children: List = emptyList(), metas: MetaContainer = emptyMetaContainer() ): SampleTypeDomain.Person = SampleTypeDomain.Person( first = first.asPrimitive(), last = last.asPrimitive(), children = children, metas = newMetaContainer() + metas ) // overloads of [person] omitted for brevity. } // ... other stuff } ``` ## Sum Type ```kotlin class CalculatorAst private constructor() { // ... other stuff sealed class Expr(override val metas: MetaContainer = emptyMetaContainer()) : CalculatorAstNode() { class Lit( val value: LongPrimitive, override val metas: MetaContainer = emptyMetaContainer() ) : Expr() { // ... API same as "person" type from a previous example } class Binary( val op: Operator, val left: Expr, val right: Expr, override val metas: MetaContainer = emptyMetaContainer() ) : Expr() { // ... API same as "person" type from a previous example } } // ... other stuff } ``` The builder API shown in previous examples works here as well: ```Kotlin // 1 + 2 * 3 val expr = CalculatorAst.build { binary( plus(), lit(1), binary( times(), lit(2), lit(3) ) ) } ``` ## Converter — `Convert` PIG generates a facility that allows an instance of a Sum Type to be converted to any other type. Two examples are provided below. ```kotlin val expr = CalculatorAst.build { // 1 + 2 * 3 (from the example above) } /** Evaluates an instance of CalculatorAst.Expr */ class CalculatorAstEvaluator : CalculatorAst.Expr.Converter { override fun convertLit(node: CalculatorAst.Expr.Lit): Long = node.value.value override fun convertBinary(node: CalculatorAst.Expr.Binary): Long { val leftValue = convert(node.left) val rightValue = cvonvert(node.right) when (node.op) { is CalculatorAst.Operator.Plus -> leftValue + rightValue is CalculatorAst.Operator.Minus -> leftValue - rightValue is CalculatorAst.Operator.Times -> leftValue * rightValue is CalculatorAst.Operator.Divide -> leftValue / rightValue is CalculatorAst.Operator.Modulo -> leftValue % rightValue } } } val evaluator = CalculatorAstEvaluator() println(evaluator.convert(expr)) // prints: 7 /** Converts an instance of CalculatorAst.Expr into source code. */ class ExprStringifier : CalculatorAst.Expr.Converter { override fun convertLit(node: CalculatorAst.Expr.Lit): String = node.value.toString() override fun convertBinary(node: CalculatorAst.Expr.Binary): String { val leftString = convert(node.left) val rightString = cvonvert(node.right) return when (node.op) { is CalculatorAst.Operator.Plus -> "$leftString + $rightString" is CalculatorAst.Operator.Minus -> "$leftString - $rightString" is CalculatorAst.Operator.Times -> "$leftString * $rightString" is CalculatorAst.Operator.Divide -> "$leftString / $rightString" is CalculatorAst.Operator.Modulo -> "$leftString % $rightString" } } } val stringifier = ExprStringifier() println(stringifier.convert(expr)) // prints: 1 + 2 * 3 ```