Type derivation with extra level of indirection

Hello everyone,

I’m trying to design a Scala library and would like to perform the following:

  • Define custom types where each custom type has a corresponding Scala host type (e.g., MyClass1 is a custom type that has a host type BigInt)
  • Define literal types for the custom types that has a member variable of the host type (MyLiteral[MyClass1]'s value is of BigInt type)
  • Enable the user to define compositions of my custom types which also has a literal type. We want each field to have a value of the corresponding host type (e.g., MyLiteral[MyComposition] should have two fields a: BigInt and b: String)
  • The composed types can be nested
object Main:
  ///////////////////////////////////////////////////////////////////////////////
  // Library code
  //////////////////////////////////////////////////////////////////////////////

  // We want to define custom types that corresponds to some native Scala type
  // BaseType: Base type for our custom types
  // HostType: Corresponding scala native type
  trait BaseType:
    type HostType
    def cloneType: BaseType
    def getLit(value: HostType): MyLiteral[_]

  class MyClass1(val w: Int) extends BaseType:
    type HostType = BigInt
    def cloneType: MyClass1 = new MyClass1(this.w)
    def getLit(value: HostType) = new MyLiteral[MyClass1](this.cloneType, value)

  class MyClass2(val w: Int) extends BaseType:
    type HostType = String
    def cloneType: MyClass2 = new MyClass2(w)
    def getLit(value: HostType) = new MyLiteral[MyClass2](this.cloneType, value)

  // For each of our custom types, we want to define a literal of that type
  class MyLiteral[T <: BaseType](val basetype: T, val value: basetype.HostType)

  abstract class Composition extends reflect.Selectable with BaseType

  ///////////////////////////////////////////////////////////////////////////////
  // User APIs
  //////////////////////////////////////////////////////////////////////////////

  // We would also like to compose out custom types.
  // And the composed types should also have a literal value corresponding to it.
  class MyComposition(w1: Int, w2: Int) extends Composition:
    val a = MyClass1(w1)
    val b = MyClass2(w2)

    // Question 1. We would like the derive these three functions for the subclasses of "Composition".
    // The subclasses can be nested within each other as well.
    type HostType = Record { val a: BigInt; val b: String }
    def getLit(value: HostType) = new MyLiteral[MyComposition](this.cloneType, value)
    def cloneType: MyComposition = new MyComposition(w1, w2)


  val class1 = MyClass1(3)
  val class1Literal = class1.getLit(BigInt(10))

  val comp = MyComposition(2, 3)

  // Question 2. We would like to set the literals with a API that looks like the commented line below
  // val compLit = comp.getLit(a = BigInt(1), b = "hello")

My question is:

  • Is there a way of synthesizing the HostType for any user defined composition of my custom types using Macros/reflection?
  • Is there a better way of defining getLit in such a way that we can assign values to each field (e.g., val compLit = comp.getLit(a = BigInt(1), b = "hello"))?

We are open to using case class for compositions instead of reflect.Selectable for the composed types as well.

Have you considered type classes instead? I haven’t thought about this for more than a minute, but at first glance this smells like it is likely to work better with type classes than inheritance. Modern Scala tends to favor type classes in many situations precisely because a lot of problems like derivation tend to work better that way.

(Everyone here could collectively muse about what that might look like, but first I want to check whether you’ve already considered and rejected that approach.)

1 Like