Optimizations
The technique we use to model recursive data throughout skeuomorph
is called recursion schemes. Recursion schemes allows us to model our
data as non recursive and substitute direct recursion by a call to a
type parameter in the declaration.
One of the techniques that we use from recursion schemes is
microoptimizations. We’re able to transform ASTs by just describing
the optimization we want as a function, and the library provides
mechanisms to apply that function to the AST correctly. Let’s see
namedTypes
as an example:
NamedTypes
We found that when we wanted to render a schema to its string representation and the schema had nested product types, the rendering was not correct because it was printing the definition of the product everywhere:
case class Product(field1: String, field2: case class OtherField())
^---------------------^
// see how this is not valid scala code, it should be:
case class Product(field1: String, field2: OtherField)
We solve this by substituting nested product types by its name when
they’re inside a product themselves. And we do this with the
namedTypes
combinator (in skeuomorph.mu.Optimize
):
def nestedNamedTypesTrans[T](implicit T: Basis[MuF, T]): Trans[MuF, MuF, T] = Trans {
case TProduct(name, namespace, fields, np, nc) =>
def nameTypes(f: Field[T]): Field[T] = f.copy(tpe = namedTypes(T)(f.tpe))
TProduct[T](name, namespace, fields.map(nameTypes), np, nc)
case other => other
}
def namedTypesTrans[T]: Trans[MuF, MuF, T] = Trans {
case TProduct(name, ns, _, _, _) => TNamedType[T](ns.toList, name)
case TSum(name, _) => TNamedType[T](Nil, name)
case other => other
}
def namedTypes[T: Basis[MuF, *]]: T => T = scheme.cata(namedTypesTrans.algebra)
def nestedNamedTypes[T: Basis[MuF, *]]: T => T = scheme.cata(nestedNamedTypesTrans.algebra)
and then apply the namedTypes
combinator to the AST:
def ast = Mu(TNull[Mu[MuF]]())
val optimization = Optimize.namedTypes[Mu[MuF]]
// optimization: Mu[MuF] => Mu[MuF] = <function1>
optimization(ast)
// res0: Mu[MuF] = Default(fmf = TNull())