如何确保某些类型具有特定成员

时间:2011-09-06 15:14:14

标签: scala

我正在寻找一种方法来将我的多态类限制为具有特定成员函数的类型。

class Table[T](bla: Array[T]) {
  val symbols = bla

  symbols.foreach( x => x * probability(x))

  def probability(t: T) : Double =  ...
}   

此代码无法编译,因为T没有成员*。我怎么能保证这一点。我不想使用继承。

编辑:概率实际上已实施。它返回一个Double。

有什么想法吗?

4 个答案:

答案 0 :(得分:3)

问题可以通过不同方式解决。例如,如果您只是希望类型T具有某种方法(并且您不关心此方法是否在对象上定义,或者是否存在将对象转换为具有此方法的对象的隐式转换),那么您可以使用查看边界。以下示例要求类型T具有方法def *(times: Int): T

class Table[T <% {def *(times: Int): T}](bla: Array[T]) {
  bla.foreach( x => println(x * 2))
} 

new Table(Array("Hello", "World"))
// Prints:
//   HelloHello
//   WorldWorld

String没有方法*,但存在隐式转换为具有此方法的StringOps

这是另一个例子。在这种情况下,我使用方法T限制类型def size: Int

class Table[T <% {def size: Int}](bla: Array[T]) {
  bla.foreach( x => println(x.size))
} 

new Table(Array(List(1, 2, 3), List("World")))
// Prints:
//  3
//  1

List有方法size,它也可以按预期工作。

但是如果您使用的是数字值,如整数,浮点数,双精度等,这可能会更复杂。在这种情况下,我建议您使用上下文绑定。 Scala有Numeric类型类。您可以使用它来处理数字,而无需了解其类型(Numeric您可以实际使用任何可以表示为数字的内容,因此您的代码将更加通用和抽象)。以下是一个示例:

import math.Numeric.Implicits._

class Table[T : Numeric](bla: Array[T]) {
  bla.foreach( x => println(x * x))
} 

new Table(Array(1, 2, 3))
// Prints:
//  1
//  4
//  9

new Table(Array(BigInt("13473264523654723574623"), BigInt("5786785634377457457465784685683746583454545454")))
// Prints:
//  181528856924372945350108280958825119049592129
//  33486887978237312740760811863500355048015109407078304275771413678604907671187978933752066116

更新

正如您在评论中指出的那样,Numeric仍然无法解决您的问题,因为它只能处理相同类型的数字。您可以通过引入新类型来简单地解决此问题。这是一个例子:

import math.Numeric.Implicits._

trait Convert[From, To] {
    def convert(f: From): To
}

object Convert {
    implicit object DoubleToInt extends Convert[Double, Int] {
        def convert(d: Double): Int = d.toInt
    }

    implicit object DoubleToBigInt extends Convert[Double, BigInt] {
        def convert(d: Double): BigInt = d.toLong
    }
}

type DoubleConvert[To] = Convert[Double, To]

class Table[T : Numeric : DoubleConvert](bla: Array[T]) {
  bla.foreach( x => println(x * implicitly[DoubleConvert[T]].convert(probability(x))))
  def probability(t: T) : Double = t.toDouble + 2.5
} 

new Table(Array(1, 2, 3))
new Table(Array(BigInt("13473264523654723574623"), BigInt("5786785634377453434")))

对于DoubleConvert类型类和T : Numeric : DoubleConvert上下文绑定,您不仅要说T应该是某种数字,而且应该存在一些证据,它可以从Double转换而来。您正在使用implicitly[DoubleConvert[T]]收到此类证据,然后您正在使用它将Double转换为T。我为Double定义了Convert - &gt; Int和Double - &gt; BigInt,但您也可以根据需要定义自己的类型。

答案 1 :(得分:1)

使用scala的结构类型:http://markthomas.info/blog/?p=66

您的代码最终会看起来像这样:

class Table[T <: {def *(i:Int): T}](bla: Array[T]) {
    ...
}

答案 2 :(得分:1)

其他人正在回答“结构类型”。非常正确,因为这是正确的答案!

不是重复明显,我会扩展它。从Easy Angel的回复中获取片段:

class Table[T <% {def *(times: Int): T}](bla: Array[T]) {
  bla foreach {x => println(x*2)}
} 

如果您发现多次使用相同的表达式{def *(times: Int): T},则可以为其创建类型别名

type HasTimes = {def *(times: Int): T}

class Table[T <% HasTimes](bla: Array[T]) {
  bla foreach {x => println(x*2)}
} 

答案 3 :(得分:0)

如果您不想使用继承,则可以应用的唯一其他限制是上下文绑定。因此,如果您有一个可以使用的类列表,则为每个类X创建一个implicit object HasStar[X]并使用类似T:HasStar的上下文绑定。我知道这可能不是你想要的,但我认为没有更好的选择。