如何绕过或满足 Thrift 类的 Scala 泛型类型

use*_*563 7 java generics scala thrift

我正在尝试编写一个通用的 scala 实用程序函数来处理 Apache Thrift 生成的 Java 类。所有 Thrift 生成的 Java 类都TBase使用以下签名扩展接口:

public interface TBase<T extends TBase<?,?>, F extends TFieldIdEnum>
Run Code Online (Sandbox Code Playgroud)

这在创建通用节俭实用程序函数时总是被证明是有问题的,在 Java 世界中,我通常通过@SuppressWarnings("rawtypes")知道我正在正确处理它并有效地忽略编译器告诉我它无法验证匹配类型来解决这个问题.

在 scala 世界中,我似乎没有奢侈或忽略这些类型,并且遇到了我似乎无法弄清楚如何满足泛型类型要求的情况。

所以我创建了自己的函数,它将给定的非节俭对象转换为所请求的节俭类的实例,其签名类似于:

def myFunc[T <: TBase[T, E], E<: TFieldIdEnum](obj: Any, clazz: Class[T]): T = {
Run Code Online (Sandbox Code Playgroud)

所以我的问题是,要调用此函数,我必须执行以下操作:

myFunc[MyThriftClass,MyThriftClass._Fields]( obj, classOf[MyThriftClass] )
Run Code Online (Sandbox Code Playgroud)

如果没有明确指定第二个类型参数,编译将失败:

error: inferred type arguments [MyThriftClass,Nothing] do not conform to method myFunc's type parameter bounds [T <: org.apache.thrift.TBase[T,E],E <: org.apache.thrift.TFieldIdEnum]
    myFunc( null, classOf[MyThriftClass])
    ^
error: type mismatch;
found   : Class[MyThriftClass](classOf[MyThriftClass])
required: Class[T]
   myFunc( null, classOf[MyThriftClass])
Run Code Online (Sandbox Code Playgroud)

当我传递两个类型参数时,一切正常,尽管我更愿意只需要传递类。

但是如果我在编译时不知道类型,我似乎无法弄清楚如何让它工作。

例如,thrift 元数据是通过FieldValueMetaData对象定义的,当有子结构时,提供的元数据对象StructMetaData包含一个字段

public final Class<? extends TBase> structClass;
Run Code Online (Sandbox Code Playgroud)

鉴于此类缺少类型参数,我不知道如何在满足泛型类型约束的同时使用它调用我的方法。在递归的情况下,我设法通过将 substruct 类转换Class[T]为不正确的实例来绕过它,因为它是父类型而不是子类型,但由于类型擦除,它使编译器满意并在运行时工作。但是,如果我还没有一个泛型类型可以转换为我不知道如何处理这个,除了可能是一种非常hacky 的方法,我将它转换为 TBase 类的某个假 shell 只是为了让编译器满意。

有没有一种正确的方法来设置它以使编译器对真实类型感到满意?或者,有没有办法绕过这个类似于我在纯 Java 中使用@SuppressWarnings("rawtypes"). 否则,如果不是那么也许我只需要将此逻辑移植到 Java 并从 Scala 调用它以使一切顺利。在这样做之前,我要么想了解我所缺少的东西,要么想了解为什么我想要的东西是不可能的。

编辑:

实际上,我正在寻找的是在 Scala 中以某种方式调用给定Tbase[_,_]. 在Java中,我会通过抑制原始类型来做到这一点,但在scala中,我想知道一种满足类型并允许调用我的方法的方法,或者一种抑制它们并允许调用我的方法的方法。

编辑2:

想出一种方法来做我想做的事,但仍然在寻找更清洁的解决方案(如果存在)。我最终得到的是以下内容:

def myFunc[T <: TBase[_,_],T2 <: TBase[T2, E2], E2<: TFieldIdEnum](obj: Any, clazz: Class[T]): T = {
  _myFunc[T2,E2]( obj, clazz.asInstanceOf[Class[T2]] ).asInstanceOf[T]
}

def _myFunc[T <: TBase[T, E], E<: TFieldIdEnum](obj: Any, clazz: Class[T]): T = {
Run Code Online (Sandbox Code Playgroud)

老实说,我不知道它为什么起作用,因为我没有指定什么类型T2或者E2是什么类型,但这似乎确实使它编译和运行。

Iva*_*Kam 5

简短的回答,这可能符合您的目的。

这种说法是不正确的。

在 scala 世界中,我似乎没有忽略这些类型的奢侈

在 Scala 世界中,您可以使用forSome以下方法擦除泛型类型参数:

def myFunc[T <: TBase[U, E] forSome {type U; type E}, E<: TFieldIdEnum](obj: Any, clazz: Class[T])
Run Code Online (Sandbox Code Playgroud)

构造T <: TBase[U, E] forSome {type U; type E}声明 T 是具有某些但不重要的类型参数的 TBase 的子类型。
你可以缩短forSome为_:

def myFunc[T <: TBase[_, _], E<: TFieldIdEnum](obj: Any, clazz: Class[T])
Run Code Online (Sandbox Code Playgroud)

可选地,您可以通过调用clazz隐式类标记来删除参数:

def myFunc[T <: TBase[_, _]: ClassTag, E<: TFieldIdEnum](obj: Any) = {
  val c = implicitly[ClassTag[T]].runtimeClass
Run Code Online (Sandbox Code Playgroud)

在不删除通用参数的情况下使用解决方案回答更长的时间。

在这段代码中:

def myFunc[T <: TBase[T, E], E<: TFieldIdEnum](obj: Any, clazz: Class[T]): T = ???
Run Code Online (Sandbox Code Playgroud)

似乎您尝试进行某种“类型参数柯里化”,然后进行类型参数推断。奇怪的是,scala 没有默认的类型参数柯里化,某些类型参数可以省略并由scalac. 在 scala 中,您应该明确设置要设置的类型参数,以及要从用法中推断出的类型参数。这是使用辅助类及其伴生对象完成的。拥有

  def foo_1[G[_], A](fb: G[A]):G[A] = ???
Run Code Online (Sandbox Code Playgroud)

这就是所谓的:

  val v: List[Int] = ???
  foo_1[List, Int](v)
Run Code Online (Sandbox Code Playgroud)

因为,您知道,在某些情况下无法轻易推断出更高的种类。然后我们介绍一种特殊的空case class foo[G[_]](),它是用来承载这种类型的。我们可以通过 apply 方法创建它的实例以获得良好的语法。


  object foo{
    def apply[G[_]]: foo[G] = new foo[G]()
  }
Run Code Online (Sandbox Code Playgroud)

然后我们可以放置另一个apply方法来引入第二个类型参数B

  case class foo[G[_]](){
    def apply[A](fa: G[A]): G[A] = foo_1[G,A](fa)
  }
Run Code Online (Sandbox Code Playgroud)

比我们可以拥有简洁的无样板语法:

  val v: List[Int] = ???

  val v1 = foo_1[List, Int](v)
  val v2 = foo[List](v)
} 
Run Code Online (Sandbox Code Playgroud)

请注意,由于相关的语法糖,foo[List](v)foo.apply[List].apply[Int](v)只是相同的apply

此方法不起作用的地方是B编译器无法推断类型参数的地方。我希望你的情况不是那样。

最后,同样重要的是,因为您没有要求这样做,但是,您实际上可以避免clazz: Class[T]作为函数参数显式传递,而是可以将其转换为隐式参数:

def myFunc[T <: TBase[T, E], E<: TFieldIdEnum](obj: Any)(implicit ct: ClassTag[T]): T = ???
Run Code Online (Sandbox Code Playgroud)

并从您的函数中将其作为常规变量访问。为了包装一切,您必须通过辅助类传递这个类标签。此外,要使用该技巧,您应该消除 T 对类型 E 的依赖,以允许通过forSome.

  trait F[A, B]
  trait U

  object foo {
    def apply[T <: F[T, E] forSome {type E}]: foo[T] = new foo[T]
  }
Run Code Online (Sandbox Code Playgroud)

并且,为了恢复类型 E,您可以要求编译器E通过<:<由编译器生成的类型的隐式证据推断出这样的参数,即两者都满足要求:E <: U 和 T <: F[T,E] 。

  case class foo[T <: F[T, E] forSome {type E}]() {
    def apply[E <: U](a: Any)(implicit e: <:<[T, F[T, E]], ct: ClassTag[T]): T = {
      ???
      //here goes your code.
    }
  }
Run Code Online (Sandbox Code Playgroud)

我希望你能以证据的形式使用事实 T <: F[T,E](这也是 T => F[T,E] 的子类型)。

这是应该如何使用语法:

  trait E extends U
  trait T extends F[T,E]

  foo[T].apply("abc")
Run Code Online (Sandbox Code Playgroud)