【问题标题】:How to return wildcard generic?如何返回通配符泛型?
【发布时间】:2019-09-04 04:19:49
【问题描述】:

我有一个带参数的类型别名,我想从一个方法返回不同参数类型的实例:

type TC[T] = (ClassTag[T], Option[T])

def gen(x: Int): TC[_] = x match {
  case 0 => (classTag[Int], Option[Int](0))
  case _ => (classTag[String], Option[String](""))
}

这不起作用并给我错误:

错误:类型不匹配; 找到 : (scala.reflect.ClassTag[_ >: Int with String], Option[Any]) 必需:TC[] (扩展为) (scala.reflect.ClassTag[$1], Option[_$1]) forSome { type _$1 }

并且我尝试使用Any而不是通配符_,还是不行:

def gen(x: Int): TC[Any]

在第 2 行:错误:类型不匹配; 发现:scala.reflect.ClassTag[Int] 必需:scala.reflect.ClassTag[Any] 注意:Int <: any trait classtag t>_ <: Any 之类的通配符类型。 (SLS 3.2.10) case _ => (classTag[String], Some("")) ^ 第 3 行:错误:类型不匹配; 找到:scala.reflect.ClassTag[String] 必需:scala.reflect.ClassTag[Any] 注意:String <: any trait classtag t>_ <: Any 之类的通配符类型。 (SLS 3.2.10)

如何做到这一点?

【问题讨论】:

    标签: scala typeclass covariance existential-type singleton-type


    【解决方案1】:

    最好返回特定类型而不是存在。如果您希望gen 根据其参数返回不同的类型,那么实际上gen 是一个多态函数。使用类型类和单例类型尝试以下方法。

    type TC[T] = (ClassTag[T], Option[T])
    
    trait Gen[X <: Int] {
      type Out
      def apply(x: X): Out
    }
    trait LowPriorityGen {
      type Aux[X <: Int, Out0] = Gen[X] { type Out = Out0 }
      def instance[X <: Int, Out0](f: X => Out0): Aux[X, Out0] = new Gen[X] {
        override type Out = Out0
        override def apply(x: X): Out0 = f(x)
      }
    
      implicit def default[X <: Int : ValueOf]: Aux[X, TC[String]] = instance(_ => (classTag[String], Option[String]("")))
    }
    object Gen extends LowPriorityGen {
      implicit val zero: Aux[0, TC[Int]] = instance(_ => (classTag[Int], Option[Int](0)))
    }
    
    def gen[X <: Int with Singleton](x: X)(implicit g: Gen[X]): g.Out = g(x)
    
    gen(0) //(Int,Some(0))
    gen(1) //(java.lang.String,Some())
    

    原因与previous question 中的类似。 ClassTagOption 有不同的方差。

    试试

    type TC[T] = (ClassTag[_ <: T], Option[T])
    
    def gen(x: Int): TC[_] = x match {
      case 0 => (classTag[Int], Option[Int](0))
      case _ => (classTag[String], Option[String](""))
    }
    

    即使您无法在类型中编码所需的属性,您仍然可以在编译时使用模式匹配右侧的check 对其进行检查。

    def gen(x: Int): (ClassTag[_], Option[_]) = x match {
      case 0 => check(classTag[Int], Option[Int](0))
      case _ => check(classTag[String], Option[String](""))
    }
    
    def check[T](classTag: ClassTag[T], option: Option[T]): (ClassTag[T], Option[T]) = (classTag, option)
    

    【讨论】:

    • 不幸的是,值 x 在编译时是未知的。为什么在我的情况下extential(通配符)和Any都不起作用?
    • 为什么当我们有一个不变的泛型类型时,我们需要使用_ &lt;: T?那和class Foo[+T]之类的有什么区别?
    • @texasbruce _ &lt;: T 是使不变协变的方法。没有不同。我们可以写type ClassTag[+T] = scala.reflect.ClassTag[_ &lt;: T]def classTag[T: scala.reflect.ClassTag]: ClassTag[T] = scala.reflect.classTag[T]type TC[T] = (ClassTag[T], Option[T])
    • 所以type CT[+T] = scala.reflect.ClassTag[_ &lt;: T]type CT[T] = scala.reflect.ClassTag[_ &lt;: T] 是相同的,并且使CT 协变?
    • @texasbruce scala.reflect.ClassTag 是不变的,scala.reflect.ClassTag[_ &lt;: T] 是协变的(相对于 T),CT in CT[+T] = scala.reflect.ClassTag[_ &lt;: T] 是协变的,CT in CT[T] = scala.reflect.ClassTag[_ &lt;: T] 是协变的好吧。
    【解决方案2】:

    原来是因为Tuple4 类型参数是协变的:Tuple4[+T1, +T2, +T3, +T4],它不适用于像ClassTag 这样的不变类型类。

    我创建了一个采用不变类型参数的包装类:

    case class TC[T](ct: ClassTag[T], o: Option[T])
    
    def gen(x: Int): TC[_] = x match {
      case 0 => TC(classTag[Int], Option[Int](0))
      case _ => TC(classTag[String], Option[String](""))
    }
    

    瞧,它起作用了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-10
      • 1970-01-01
      • 1970-01-01
      • 2020-03-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多