开发者

Scala: Whats the best way to do numeric operations in generic classes?

开发者 https://www.devze.com 2022-12-13 09:55 出处:网络
In Scala, I\'d like to be able to write generic classes which use operators like >, /, * etc, but I don\'t see how to constrain T such that this will work.

In Scala, I'd like to be able to write generic classes which use operators like >, /, * etc, but I don't see how to constrain T such that this will work.

I looked into constraining T with Ordered[T], but that doesn't seem to work since only RichXXX (e.g. RichInt) extend it, not Int etc. I also saw Numeric[T], is this only available in Scala 2.8?

Here is a specific example:

class MaxOfList[T](list: List[T] ) {
  def max = {
    val seed: Option[T] = None

    list
      .map( t => Some(t))
      // Get the max      
      .foldLeft(seed)((i,m) => getMax(i,m) )
  }

  private def getMax(x: Option[T], y: Option[T]) = {
    if ( x.isDefined && y.isDefined )
      if ( x > y ) x else y
    else if ( x.isDefined )
      x
    else
      y
  }
}

This class won't compile, because there are many Ts which don't support > etc.

Thoughts?

For now I've used a MixIn trait to get around this:

/** Defines a trait that can get the max of two generic values
 */
trait MaxFunction[T] {
  def getMax(x:T, y:T): T
}

/** An implementation of MaxFunction for Int
 */
trait IntMaxFunction extends MaxFunction[Int] {
  def getMax(x: Int, y: Int) = x.max(y)
} 

/** An implementation of MaxFunction for Double
 */
trait DoubleMaxFunction extends MaxFunction[Double] {
  def 开发者_Go百科getMax(x: Double, y: Double) = x.max(y)
} 

Which if we alter the original class can be mixed in at instantiation time.

P.S. Mitch, inspired by your re-write of getMax, here is another:

  private def getMax(xOption: Option[T], yOption: Option[T]): Option[T] = (xOption,yOption) match {
    case (Some(x),Some(y)) => if ( x > y ) xOption else yOption
    case (Some(x), _) => xOption
    case _ => yOption
  }


You can use View Bounds.

In short, def foo[T <% U](t: T) is a function that will take any T that either is or can be implicitly converted to a U. Since an Int can be converted to a RichInt (which contains your desired method), this is an excellent example of usage.

class MaxOfList[T <% Ordered[T]](list: List[T] ) {
  def max = {
    val seed: Option[T] = None
    list.foldLeft(seed)(getMax(_,_))
  }

  private def getMax(xOption: Option[T], y: T) = (xOption, y) match {
    case (Some(x), y) if ( x > y ) => xOption
    case (_, y) => Some(y)
  }
}

PS - I rewrote your getMax(...) method to compare the values instead of the options themselves, and used pattern matching instead of isDefined(...)

PPS - Scala 2.8 will have a Numeric trait that may be of use. http://article.gmane.org/gmane.comp.lang.scala/16608


Addendum

Just for giggles, here's the super-compact version that eliminates the getMax method altogether:

class MaxOfList[T <% Ordered[T]](list: List[T] ) {
  def max = list.foldLeft(None: Option[T]) {
      case (Some(x), y) if ( x > y ) => Some(x)
      case (_, y) => Some(y)
  }
}

Yet Another Addendum

This version would be more efficient for large lists... avoids creation of Some(x) for each element:

class MaxOfList[T <% Ordered[T]](list: List[T] ) {
  def max = {
    if (list.isEmpty) None
    else Some(list.reduceLeft((a,b) => if (a > b) a else b))
  }
}

Last One, I Promise!

At this point, you can just ditch the class and use a function:

  def max[T <% Ordered[T]](i: Iterable[T]) = {
    if (i.isEmpty) None
    else Some(i.reduceLeft((a,b) => if (a > b) a else b))
  }


With Numeric[T] in scala 2.8,

scala> case class MaxOfList[T : Numeric](list: List[T]) {
     |     def max = if(list.isEmpty) None else Some(list.max)
     | }
defined class MaxOfList

scala> MaxOfList(1::2::3::9::7::Nil).max

res1: Option[Int] = Some(9)

scala> MaxOfList(1.5::3.9::9.2::4.5::Nil).max

res2: Option[Double] = Some(9.2)

scala> MaxOfList(Nil: List[Byte]).max

res3: Option[Byte] = None

scala>


As answered by Mitch Blevins, that's what view bounds were made for. Now, while Numeric is something only added on Scala 2.8, not that it does not depend on any Scala 2.8 feature. One could do the same thing on Scala 2.7.

You may be interested in some generic code snippets I wrote for Rosetta Code:

  • Average/Mean
  • Average/Median
  • Average/Mode
  • Quicksort
0

精彩评论

暂无评论...
验证码 换一张
取 消