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(_)