如何解决Scala 3-一阶类型的InverseMap
我正在尝试创建一个函数,该函数采用一个类型较高的元组,并将一个函数应用于类型较高的类型中。
在下面的示例中,有一个trait Get[A]
,它是我们的上等类型。还有一个Get的元组:(Get[String],Get[Int])
以及(String,Int) => Person
中的函数。
Scala-3具有一个称为InverseMap的Match-Type,它将类型(Get [String],Get [Int])转换为本质上是类型(String,Int)的>
因此,最终目标是编写一个函数,该函数可以使用任意数量的Get[_]
类型的元组,以及一个其输入与InserveMap类型匹配的函数,最后返回一个Get[_]
,其中包装的类型是函数的结果。
我试图在下面创建一个名为genericF
的函数来显示所需的行为,尽管它可能并不正确-但我认为它至少确实显示了正确的意图。
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)
我在这里设置了Scastie:https://scastie.scala-lang.org/OleTraveler/QIyNHPLHQIKPv0lgsYbujA/23
解决方法
您的代码已经差不多compiling了-唯一的问题是fPerson
的类型为(String,Int) => Person
而不是((String,Int)) => Person
(采用元组而不是2个单独的参数)。
编辑:尽管对于TupleXXL而言,它可能更有效,但该解决方案下面的解决方案并不理想。这是带有类型类(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)))
编辑:操作员Travis Stevens使用AllGs
设法使解决方案在底部运行而无需创建IsMappedBy
。这就是他们得到的(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,Get],B](
t: T,f: I => B
)(using Tuple.IsMappedBy[Get][T]): Get[B] = Put(f(extract(t)))
here是使用genericF
实现Tuple.InverseMap
的一种方式(请注意,我将两个参数切换为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
用于编译PolyFunction
,因为它要求您将类型构造函数应用于其类型参数。
AllGs
用于验证元组仅由Get
组成,因为正如Dmytro Mitin指出的那样,否则它不是类型安全的。如果全部为Get
,则类型变为DummyImplicit
,Scala为我们提供了该类型。否则为Nothing
。我猜想它可能会与其他范围内的隐式/给定的Nothing
冲突,但是如果您已经有一个隐式/给定的,反正会被搞砸了:)。
请注意,只有当您拥有Get
时,此方法才有效;如果您还希望它适用于(Put[String],GetSubclass[Int])
这样的元组,则需要进行一些修改。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。