Scala 3 - 在一阶类型上提取包装器元组和 InverseMap [英] Scala 3 - Extract Tuple of wrappers and InverseMap on First Order Type

查看:53
本文介绍了Scala 3 - 在一阶类型上提取包装器元组和 InverseMap的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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.

在下面的例子中,有一个 trait Get[A] 是我们的高级类型.还有一个 Get 的元组:(Get[String],Get[Int]) 以及来自 (String,Int) => 的函数.人.

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 有一个名为 InverseMap 的匹配类型,它将类型 (Get[String], Get[Int]) 转换为本质上的类型 (String,Int).

Scala-3 has a Match-Type called InverseMap which converts the type (Get[String], Get[Int]) into what is essentially the type (String,Int).

所以最终目标是编写一个函数,该函数可以接受任意数量的 Get[_] 类型的元组和一个输入与 InserveMap 类型匹配的函数,并最终返回一个 Get[_],其中包装的类型是函数的结果.

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.

我尝试在下面创建一个名为 genericF 的函数来显示所需的行为,尽管它可能不正确——但我认为它至少显示了正确的意图.

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)

我在这里设置了一个 Scastie:https://scastie.scala-lang.org/OleTraveler/QIyNHPLHQIKPv0lgsYbujA/23

I have set up a Scastie here: https://scastie.scala-lang.org/OleTraveler/QIyNHPLHQIKPv0lgsYbujA/23

推荐答案

你的代码几乎是编译 已经 - 唯一的事情是 fPerson 的类型是 (String, Int) =>Person 而不是 ((String, Int)) =>Person(采用元组而不是 2 个单独的参数).

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).

下面的解决方案并不好,尽管它对于 TupleXXL 可能更有效.这是一个带有类型类的更好的版本(Scastie):

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


这里是您实现 genericF 的一种方法,使用Tuple.InverseMap(注意我把两个参数换成了genericF:


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 是为了让 PolyFunction 编译,因为它需要你对它的类型参数应用一个类型构造函数.

ExtractG is to make the PolyFunction compile, because it requires you apply a type constructor to its type parameter.

AllGs 用于验证元组是否仅由 Get 组成,因为正如 Dmytro Mitin 指出的那样,否则它不是类型安全的.如果都是Get,类型就变成了DummyImplicit,这是Scala 为我们提供的.否则,它是Nothing.我想它可能与范围内的其他隐式/给定 Nothing 冲突,但如果你已经有了一个,无论如何你都被搞砸了.

AllGs is to verify that the tuple consists only of Gets, because as pointed out by Dmytro Mitin, it isn't typesafe otherwise. If it's all Gets, the type becomes DummyImplicit, which Scala provides for us. Otherwise, it's Nothing. I guess it could conflict with other implicit/given Nothings in scope, but if you do have one already, you're screwed anyways.

请注意,这仅在您拥有 Get 时才有效,如果您还希望它适用于 (Put[String], GetSubclass[Int]) 等元组,则需要进行一些修改.

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]).

Travis Stevens 是 OP,通过使用 IsMappedBy,设法使上面的解决方案在不创建 AllGs 的情况下工作.这就是他们得到的 (Scastie):

Travis Stevens, the OP, has managed to get the solution above this one 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)))


这里有一个使用依赖类型,只是为了好玩(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 还是有必要的.

I was hoping Extract wouldn't compile for tuples like (Put("foo"), 3), but unfortunately, AllGs is still necessary.

这篇关于Scala 3 - 在一阶类型上提取包装器元组和 InverseMap的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆