Scala 3 - 在一阶类型上提取包装器和 InverseMap 的元组
Scala 3 - Extract Tuple of wrappers and InverseMap on First Order Type
我正在尝试创建一个函数,该函数采用更高类型的元组并将函数应用于更高类型中的类型。
在下面的示例中,有一个 trait Get[A]
是我们的高等类型。还有一个 Get 的元组:(Get[String],Get[Int])
以及来自 (String,Int) => Person
.
的函数
Scala-3 有一个名为 InverseMap 的匹配类型,它将类型 (Get[String], Get[Int]) 转换为本质上的类型 (String,Int)。
所以最终目标是编写一个函数,该函数可以接受具有任意数量 Get[_]
类型的元组和一个输入与 InserveMap 类型匹配的函数,最后 return a 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)))
Here 是您可以使用 Tuple.InverseMap
实现 genericF
的一种方式(请注意,我将两个参数切换为 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
。我想它可能会与范围内的其他 implicit/given Nothing
冲突,但如果你已经有一个,那你就完蛋了。
请注意,这仅在您拥有 Get
时有效,如果您还希望它适用于像 (Put[String], GetSubclass[Int])
.
这样的元组,则需要进行一些修改
OP 特拉维斯·史蒂文斯 (Travis Stevens) 通过使用 IsMappedBy
设法在不创建 AllGs
的情况下使上述解决方案生效。这是他们得到的 (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)))
这里有一个使用依赖类型,只是为了好玩 (Scastie):
type Extract[T <: Tuple] <: Tuple = T match {
case EmptyTuple => EmptyTuple
case Get[a] *: t => a *: Extract[t]
}
type AllGs[T <: Tuple] = T match {
case EmptyTuple => DummyImplicit
case Get[_] *: t => AllGs[t]
case _ => Nothing
}
def genericF[T <: Tuple : AllGs, B](
t: T,
f: Extract[t.type] => B
): Get[B] = {
def extract[T <: Tuple](t: T): Extract[T] = t match {
case _: EmptyTuple => EmptyTuple
case (head *: tail): (Get[_] *: _) => head.get *: extract(tail)
}
Put(f(extract(t)))
}
我希望 Extract
不会像 (Put("foo"), 3)
这样的元组编译,但不幸的是,AllGs
仍然是必需的。
我正在尝试创建一个函数,该函数采用更高类型的元组并将函数应用于更高类型中的类型。
在下面的示例中,有一个 trait Get[A]
是我们的高等类型。还有一个 Get 的元组:(Get[String],Get[Int])
以及来自 (String,Int) => Person
.
Scala-3 有一个名为 InverseMap 的匹配类型,它将类型 (Get[String], Get[Int]) 转换为本质上的类型 (String,Int)。
所以最终目标是编写一个函数,该函数可以接受具有任意数量 Get[_]
类型的元组和一个输入与 InserveMap 类型匹配的函数,最后 return a 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)))
Here 是您可以使用 Tuple.InverseMap
实现 genericF
的一种方式(请注意,我将两个参数切换为 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
。我想它可能会与范围内的其他 implicit/given Nothing
冲突,但如果你已经有一个,那你就完蛋了。
请注意,这仅在您拥有 Get
时有效,如果您还希望它适用于像 (Put[String], GetSubclass[Int])
.
OP 特拉维斯·史蒂文斯 (Travis Stevens) 通过使用 IsMappedBy
设法在不创建 AllGs
的情况下使上述解决方案生效。这是他们得到的 (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)))
这里有一个使用依赖类型,只是为了好玩 (Scastie):
type Extract[T <: Tuple] <: Tuple = T match {
case EmptyTuple => EmptyTuple
case Get[a] *: t => a *: Extract[t]
}
type AllGs[T <: Tuple] = T match {
case EmptyTuple => DummyImplicit
case Get[_] *: t => AllGs[t]
case _ => Nothing
}
def genericF[T <: Tuple : AllGs, B](
t: T,
f: Extract[t.type] => B
): Get[B] = {
def extract[T <: Tuple](t: T): Extract[T] = t match {
case _: EmptyTuple => EmptyTuple
case (head *: tail): (Get[_] *: _) => head.get *: extract(tail)
}
Put(f(extract(t)))
}
我希望 Extract
不会像 (Put("foo"), 3)
这样的元组编译,但不幸的是,AllGs
仍然是必需的。