【问题标题】:Scala: how to specify type parameter bounds implying equality?Scala:如何指定暗示相等的类型参数边界?
【发布时间】:2011-12-14 18:03:38
【问题描述】:

不要被冗长的文字吓到,要点很简单,但需要一些代码来说明问题。 :-)

设置:

假设我想创建一个特征,这里建模为某种Converter,它本身是通用的,但有一个类型化的方法convert(),它返回一个适当类型化的结果对象,比如Container[T]

 trait Converter {
   def convert[T]( input: T ) : Container[T]
 } 
 trait Container[T]  // details don't matter

我的问题是关于方法的类型约束,特别是对于强制平等,并且有两个密切相关的部分。

第 1 部分:现在说有一种特别适合基于数组的内容的专用容器类型,如下所示:

 object Container {
   trait ForArrays[U] extends Container[Array[U]] 
 }

鉴于这种可能性,我现在想将 Converter 特别是 convert() 方法的返回类型专门化为专门的 Container.ForArrays 类型:

 object Converter {
   trait ForArrays extends Converter {
     // the following line is rubbish - how to do this right?
     def convert[E,T <: Array[E]]( input: T ) : Container.ForArrays[E]
   } 
 }

这样我就可以做这样的事情了:

val converter = new Converter.ForArrays { ... }
val input = Array( 'A', 'B', 'C' )   
val converted : Container.ForArrays[Char] = converter.convert( input )

基本上,如果已知转换器的类型为Converter.ForArrays,我希望 Scala 也将convert[Char]() 的专用返回类型推断为Container.ForArrays[Char],即匹配的容器类型加上输入的数组类型。这是可能的吗?如果是这样,我该怎么做?例如。如何在 convert() 上指定类型参数/边界(所提供的只是一个替身——我不知道该怎么做)。哦,自然是这样它仍然会覆盖它的超级方法,否则什么也得不到。

第 2 部分: 作为后备,如果这不可能,我当然可以将 convert 函数向下推到以数组为中心的变体中,如下所示:

 trait Converter   // now pretty useless as a shared trait
 object Converter {
   trait ForValues extends Converter { 
     def convert[T]( input: T ) : Container[T] 
   }
   trait ForArrays extends Converter {
     def convert[E]( input: Array[E] ) : Container.ForArrays[E]
   } 
 }

好的。现在假设我有一个更专业的Converter.ForArrays.SetBased,它可以在内部使用一组 E 类型的元素(与“输入”数组元素类型相同)在转换期间执行一些特殊的魔法。然而,该集合现在是 trait 的一个参数,如下所示:

 case class SetBased( set: Set[F] ) extends Converter.ForArrays {
   // the following line is also rubbish...
   def convert[E = F]( input: Array[E] ) : Container.ForArrays[E] = {...}
 }

再次,这是关于 convert() 方法的类型参数。这里的困难是:我如何将类的类型参数 - F - 与方法的类型参数 - E - 粘合在一起,这样 Scala 编译器只会让用户调用 convert() 并使用一个数组元素匹配集合的元素?示例:

val set = Set( 'X', 'Y', 'Z' )
val converter = new Converter.ForArrays.SetBased( set )
val input = Array( 'A', 'B', 'C' )   
val converted : Container.ForArrays[Char] = converter.convert( input )

【问题讨论】:

    标签: arrays scala types parameters type-bounds


    【解决方案1】:

    不,你不能。出于同样的原因,您不能在重写方法时缩小参数类型或扩大返回类型(但可以缩小返回类型)。但是,您可以执行以下操作(对于您的后备解决方案):

    trait Converter {
      type Constraint[T]
    } 
    
    trait ForArrays extends Converter {
      def convert[E]( input: Array[E] )( implicit ev : Constraint[T] ) : Container.ForArrays[E]
    }
    
    case class SetBased[F](set: Set[F]) extends Converter {
       type Constraint[T] = T =:= F
       def convert[E]( input: Array[E] )( implicit ev : E =:= F ) = ...
    } 
    

    【讨论】:

    • 好的,关于不能缩小参数类型的观点似乎完全适用于第一部分,因为它只允许Ts 也是Array[E]。防止持有期望 T 的超类型 Converter 的用户无意中调用期望更窄参数类型并且可能失败的子类型上的方法是有意义的。然而,在这里,我只想要缩小返回类型的好处。但编译器无法判断,而且这可能是一种罕见的极端情况,因此没有必要进行这项工作。
    【解决方案2】:

    我将假设Container.ForArraysContainer 的子类,如果没有这个,Converter.ForArrays.convert 将与被覆盖的Converter.convert 的签名不匹配

    试着写成这样:

    object Converter {
      trait ForArrays extends Converter {
        def convert[E] (input: Array[E]): Container.ForArrays[E]
      } 
    }
    

    关于您的后备解决方案。如果两种类型相同,则使用相同的类型参数!

    case class SetBased (set: Set[F]) extends Converter.ForArrays {
      def convert (input: Array[F]): Container.ForArrays[F] = {...}
    }
    

    【讨论】:

    • 如所写,您在 Converter.ForArrays 中的转换不会覆盖 Converter 中的转换(不同的参数)。是否打算超载?
    • 好点!实际上,它不需要覆盖,这取决于您打算如何使用它。
    • @Kevin 是的,我们的想法是让Container.ForArrays 成为Container 的子类。这也是我在示例中声明它的方式,但很容易被忽略。我希望重写 convert() 专门化其重写父方法的返回类型。
    • @KevinWright 关于第一个建议:正如 didierd 指出的那样,它不会覆盖父方法;这本质上是我的问题和发帖的动机。第二个建议很有趣而且很明显,所以我会尝试一下。不幸的是,这个例子被简化了一点——在真实的代码中,情况更糟,因为我的集合包含,比如说,Char,但是转换方法需要 Array[Char],专门的方法需要 Array[Array[Char]]:- ) 哦,好吧。
    猜你喜欢
    • 1970-01-01
    • 2022-12-09
    • 1970-01-01
    • 1970-01-01
    • 2018-08-18
    • 1970-01-01
    • 2020-10-14
    • 2014-12-01
    • 2019-03-16
    相关资源
    最近更新 更多