问题
I am trying to create a function, which takes a tuple of higher-kinded types and applies a function to the types within the higher-kinded types.
In the example below, there is a trait Get[A]
which is our higher-kinded type. There is also a tuple of Get's: (Get[String],Get[Int])
as well as function from (String,Int) => Person
.
Scala-3 has a Match-Type called InverseMap which converts the type (Get[String], Get[Int]) into what is essentially the type (String,Int).
So the ultimate goal is to write a function which can take a tuple with any number of Get[_]
types and a function whose input matches the InserveMap types and finally return a Get[_]
, where the wrapped type is the result of the function.
I have attempted to create a function called genericF
below to show the desired behavior, though it may not be correct -- but I think it does at least show the proper intent.
case class Person(name: String, age: Int)
trait Get[A] {
def get: A
}
case class Put[A](get: A) extends Get[A]
val t: (Get[String], Get[Int]) = (Put("Bob"), Put(42))
val fPerson: (String,Int) => Person = Person.apply _
def genericF[T<:Tuple,I<:Tuple.InverseMap[T,Get],B](f: I => B, t: T): Get[B] = ???
val person: Get[Person] = genericF(fPerson, t)
I have set up a Scastie here: https://scastie.scala-lang.org/OleTraveler/QIyNHPLHQIKPv0lgsYbujA/23
回答1:
Your code is almost compiling already - the only thing is that fPerson
is of type (String, Int) => Person
instead of ((String, Int)) => Person
(taking a tuple instead of 2 separate parameters).
Edit: The solution below this one is not nice, although it is perhaps more efficient for TupleXXL's. Here's a nicer version with typeclasses (Scastie):
val fPerson: ((String, Int)) => Person = Person.apply _
opaque type Extract[GT <: Tuple, RT <: Tuple] = GT => RT
given Extract[EmptyTuple, EmptyTuple] = Predef.identity
given [A, PG <: Tuple, PR <: Tuple](using p: Extract[PG, PR])
as Extract[Get[A] *: PG, A *: PR] = {
case h *: t => h.get *: p(t)
}
def genericF[GT <: Tuple, RT <: Tuple, B](
f: RT => B,
t: GT
)(using extract: Extract[GT, RT]): Get[B] = Put(f(extract(t)))
Edit: Travis Stevens, the OP, has managed to get the solution at the bottom to work without creating AllGs
, by using IsMappedBy
. This is what they got (Scastie):
val fPerson: ((String, Int)) => Person = Person.apply _
type ExtractG = [G] =>> G match {
case Get[a] => a
}
def extract[T <: Tuple, I <: Tuple.InverseMap[T, Get]](
t: T
)(using Tuple.IsMappedBy[Get][T]): I =
t.map {
[G] => (g: G) => g.asInstanceOf[Get[_]].get.asInstanceOf[ExtractG[G]]
}.asInstanceOf[I]
def genericF[T <: Tuple, I <: Tuple.InverseMap[T, Get], B](
t: T,
f: I => B
)(using Tuple.IsMappedBy[Get][T]): Get[B] = Put(f(extract(t)))
And here's one way you could implement genericF
using Tuple.InverseMap
(note that I switched the two parameters to genericF
:
val fPerson: ((String, Int)) => Person = Person.apply _
type ExtractG = [G] =>> G match {
case Get[a] => a
}
type AllGs[T <: Tuple] = T match {
case EmptyTuple => DummyImplicit
case Get[_] *: t => AllGs[t]
case _ => Nothing
}
def extract[T <: Tuple](t: T)(using AllGs[T]): Tuple.InverseMap[T, Get] =
t.map {
[G] => (g: G) => g.asInstanceOf[Get[_]].get.asInstanceOf[ExtractG[G]]
}.asInstanceOf[Tuple.InverseMap[T, Get]]
def genericF[B](
t: Tuple,
f: Tuple.InverseMap[t.type, Get] => B
)(using AllGs[t.type]): Get[B] = Put(f(extract(t)))
val person: Get[Person] = genericF(t, fPerson)
ExtractG
is to make the PolyFunction
compile, because it requires you apply a type constructor to its type parameter.
AllGs
is to verify that the tuple consists only of Get
s, because as pointed out by Dmytro Mitin, it isn't typesafe otherwise. If it's all Get
s, the type becomes DummyImplicit
, which Scala provides for us. Otherwise, it's Nothing
. I guess it could conflict with other implicit/given Nothing
s in scope, but if you do have one already, you're screwed anyways :).
Note that this will work only when you have Get
and will need some modification if you also want it to work for tuples like (Put[String], GetSubclass[Int])
.
来源:https://stackoverflow.com/questions/64339583/scala-3-extract-tuple-of-wrappers-and-inversemap-on-first-order-type