【问题标题】:Scala 3 - Extract Tuple of wrappers and InverseMap on First Order TypeScala 3 - 在一阶类型上提取包装器和 InverseMap 元组
【发布时间】:2021-03-26 09:33:16
【问题描述】:

我正在尝试创建一个函数,该函数采用更高种类的类型元组并将函数应用于更高种类的类型。

在下面的示例中,有一个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

【问题讨论】:

  • 我不认为Get 是 HKT - 它有一个类型参数,但该参数本身不是类型构造函数
  • 好吧,首先,fPerson 不接受 2 元组,它接受两个参数,所以我猜你想要另一对括号
  • 我相信Get 是一阶类型,因为您可以通过将类型传递给类型参数来创建正确的类型,例如Get[String] 是正确的类型。
  • 在 Scala-3 中,元组是具有 0 个或更多元素的元组的通用表示,因此不需要更多的括号。我希望创建 genericF 能够处理任何大小的元组,而不仅仅是示例中显示的 2 的大小。
  • @TravisStevens 方法参数中不需要括号:您可以调用extract(Put("Bob"), Put(42))extract((Put("Bob"), Put(42)))。但是(String,Int) =&gt; Person((String,Int)) =&gt; Person 类型是不同的。

标签: scala tuples dotty scala-3


【解决方案1】:

您的代码几乎已经是 compiling - 唯一的问题是 fPerson 的类型是 (String, Int) =&gt; Person 而不是 ((String, Int)) =&gt; 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 用于验证元组是否仅包含Gets,因为正如 Dmytro Mitin 所指出的,否则它不是类型安全的。如果都是Gets,那么类型就变成了DummyImplicit,这是Scala为我们提供的。否则,它是Nothing。我想它可能会与范围内的其他隐式/给定的Nothings 冲突,但如果你已经有一个,那么无论如何你都搞砸了。

请注意,这仅在您拥有 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 仍然是必需的。

【讨论】:

  • extract 不是类型安全的。如果我们调用extract((1, 2)),它将编译但在运行时抛出ClassCastException
  • @DmytroMitin 现在怎么样?可能滥用DummyImplicit,但它似乎工作
  • 相当有创意:) 我有点不解,我们使用先进的类型机器,仍然要使用很多asInstanceOf
  • 只是把它扔在那里,因为我仍在努力解决这个问题,但 IsMappedBy 会成为 AllGs 的替代品吗?
  • @user 是的,好老 Comapped + NatTRel :) github.com/milessabin/shapeless/blob/master/core/src/main/scala/…
猜你喜欢
  • 2020-10-24
  • 2010-10-09
  • 2019-01-27
  • 2021-06-01
  • 2021-04-18
  • 1970-01-01
  • 1970-01-01
  • 2022-01-02
相关资源
最近更新 更多