Select member type of a named tuple

Hi, is there a way to ‘select’ the type of a member of named tuples by name? The use case is modeling complex data types using nested named tuples to avoid name every intermediate composite type. For example:

type ComplexExternalAPIResponse = (
  statusCode: Int,
  result: (
    foo: String,
    bar: Boolean,
  )
)

def handleResult(res: ComplexExternalAPIResponse.result.type /* <= ??? */) =
  ??? // use result.foo or result.bar 

val resp: ComplexExternalAPIResponse = ???

handleResult(resp.result)

There’s type NamedTuple.Elem[X, N] that takes a numeric position N in a named tuple X, but not by name.

I’ve been actually trying this one out recently for the purpouse of showing out named tuples as a new feature, it might not be perfect, but it seemed to work for simple cases
Fully working example of selection by name inside Selectable

@main def easierStructuralTypes = 
  // Use case classes to represent possibly defined fields...
  case class Point(x: Int, y: Int, metadata: String)
  val kv = KeyValueReader[Point]("x=2;z=3;metadata=Extra info")

  assert(kv.x.contains(2)) // parses x as integer
  assert(kv.y.isEmpty) // no field y in input data
  assert(kv.metadata == Some("Extra info"))
  // kv.z // error, does not compile, Point does not have a field `z`

  // or take advantage of named tuples for ad-hoc definitions
  val kv2 = KeyValueReader[(z: Int, x: Int)]("x=42;z=21")
  assert(kv2.x.contains(42))


class KeyValueReader[T](stringRepr: String) extends Selectable {
  // Populates structural type using fields of T
  type Fields = NamedTuple.Map[NamedTuple.From[T], Option] 
  inline def selectDynamic(fieldName: String) = 
    selectField[NamedTuple.Names[NamedTuple.From[T]], NamedTuple.DropNames[NamedTuple.From[T]], fieldName.type, 0]

  private val fields = stringRepr
    .split(';').map(_.trim())
    .collect{ case s"$key=$value" => (key, value) }
    .toMap

  private inline def selectField[Names <: Tuple, FieldTypes <: Tuple, Name <: String, Idx <: Int]: Any = 
    inline compiletime.erasedValue[Names] match 
      case EmptyTuple => None // No such field found
      
      case _: (Name *: t) => 
        val parser = compiletime.summonInline[ValueParser[Tuple.Elem[FieldTypes, Idx]]]
        fields
          .get(compiletime.constValue[Name])
          .flatMap(parser.parse)

      case _: (h *: t) => selectField[t, FieldTypes, Name, scala.compiletime.ops.int.S[Idx]]
}

trait ValueParser[T]{ def parse(string: String): Option[T] }
given ValueParser[Int] = _.toIntOption
given ValueParser[String] = Some(_)
2 Likes

Thanks for sharing! Currently the only way seems to be the recursive type matching as you’ve shown.