views:

370

answers:

3

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 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
  }
+9  A: 

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))
  }
Mitch Blevins
Thanks again Mitch. I swear I tried that and couldn't get it to compile!
Alex Black
I added an update to the original question with yet another version of getMax inspired by yours.
Alex Black
I updated my getMax with yet another version inspired by yours. How many more iterations before we reach some sort of singularity?
Mitch Blevins
heh, I think we're approaching the perfect implementation as the rate of change seems to be slowing. Question: does your latest version work for sure? If I'm reading it right, I think getMax(Some(2),Some(3)) will return 2 when it should return 3. Perhaps the 2nd case should be case ((Some(x)), None)) => ?
Alex Black
You're right. Corrected.
Mitch Blevins
Rewrote again to eliminate the map over the list. Note that the optioning of the values happens in the folding function.
Mitch Blevins
+1 for None.asInstanceOf[Option[T]]. I had been creating dummy vals for this purpose.
trenton
It just keeps getting smaller :)
Alex Black
May I suggest that writing `None : Option[T]` is shorter than `None.asInstanceOf[Option[T]]`? Sure, it may require parenthesis around it, though not here, but still...
Daniel
Edited to reflect Daniel's comments.
Mitch Blevins
+2  A: 

With Numberic[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>

Eastsun
A: 

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:

Daniel