Allow structural types to be completely generated from match types

Programmatic structural types in Scala 3 have a lot of promise, especially as return values from macros (which cannot create classes via statements currently). With transparent inline, it’s possible for a macro to generate a programmatic structural type based on user input, simulating class definition generation. However, the problem with transparent inline is that the type cannot be knowable to the user (unless the user writes the type manually).

With tuples, Tuple.Map and Tuple.FlatMap allow us to create similar but different tuple definitions based on a source tuple definition. We could do the same with programmatic structural types and case classes, if not for the problem of the resulting type being unknowable unless the user manually defines it.

If structural types were able to be defined in a way that member names could be derived from a string literal (or even a literal of the ScalaName type that is the set of string singletons that are valid names in Scala), it would be possible to define match types that could build up derivative structural types piece by piece based off a case class mirror.

Basically, I want the ability to do something like this:

type Derivative[T <: Tuple, F[_]] = T match 
  case (s & Singleton & String, v) *: tail => Selectable { val $s: F[v] } & Derivative[tail]
  case EmptyTuple => Selectable

Is it forseeable that something like this could be added in Scala 3?

p.s. something like this is already achievable via Dynamic, it’s just that the compiler isn’t aware of the available members until a user tries to use them.

2 Likes

Actually it’s already possible with macros, but relies on an implicit conversion mechanism (and AFAIK, it’s the same in Scala 2). It’s very hacky, and yeah I think we need a better way to let the compiler know we want this, without relying on implicit conversions.

class Foo[T <: Tuple] extends Selectable:
  def selectDynamic(name: String): Any = ... //runtime implementation
trait Refiner[T <: Tuple]:
  type Out
object Refiner:
  transparent inline given [T <: Tuple]: Refiner[T] = ${refineMacro[T]} //compiletime implementation

//implicit conversion hack to trigger name selection
inline implicit def __refined[T <: Tuple](
    foo: Foo[T]
)(using r: Refiner[T]): r.Out = foo.asInstanceOf[r.Out]
1 Like

Implicit conversion to add the refinement didn’t even cross my mind (I had been doing it manually), mainly because I’ve been trying to avoid implicit conversions so much.