Scala: Как лучше всего выполнять числовые операции в универсальных классах?

В Scala я хотел бы иметь возможность писать универсальные классы, использующие такие операторы, как >, /, * и т. д., но я не вижу, как ограничить T так, чтобы это работало.

Я изучал ограничение T с помощью Ordered[T], но это, похоже, не работает, поскольку его расширяет только RichXXX (например, RichInt), а не Int и т. д. Я также видел Numeric[T], это доступно только в Scala 2.8?

Вот конкретный пример:

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
  }
}

Этот класс не будет компилироваться, потому что многие T не поддерживают > и т.д.

Мысли?

На данный момент я использовал трейт MixIn, чтобы обойти это:

/** 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)
} 

Что, если мы изменим исходный класс, может быть смешано во время создания экземпляра.

P.S. Митч, вдохновленный вашим переписыванием getMax, вот еще:

  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
  }

person Alex Black    schedule 08.12.2009    source источник


Ответы (3)


Вы можете использовать Просмотреть границы.

Короче говоря, def foo[T <% U](t: T) — это функция, которая принимает любой T, который либо является U, либо может быть неявно преобразован в U. Поскольку Int можно преобразовать в RichInt (который содержит желаемый метод), это отличный пример использования.

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 - я переписал ваш метод getMax(...) для сравнения значений вместо самих параметров и использовал сопоставление с образцом вместо isDefined(...)

PPS — Scala 2.8 будет иметь числовой признак, который может быть полезен. http://article.gmane.org/gmane.comp.lang.scala/16608


Дополнение

Просто для смеха, вот сверхкомпактная версия, которая полностью исключает метод getMax:

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)
  }
}

Еще одно дополнение

Эта версия будет более эффективной для больших списков... позволяет избежать создания Some(x) для каждого элемента:

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))
  }
}

Последний, обещаю!

На этом этапе вы можете просто отказаться от класса и использовать функцию:

  def max[T <% Ordered[T]](i: Iterable[T]) = {
    if (i.isEmpty) None
    else Some(i.reduceLeft((a,b) => if (a > b) a else b))
  }
person Community    schedule 09.12.2009
comment
Еще раз спасибо, Митч. Клянусь, я пробовал это и не смог его скомпилировать! - person Alex Black; 09.12.2009
comment
Я добавил обновление к исходному вопросу с еще одной версией getMax, вдохновленной вашей. - person Alex Black; 09.12.2009
comment
Я обновил свой getMax еще одной версией, вдохновленной вашей. Сколько еще итераций, прежде чем мы достигнем какой-то сингулярности? - person Mitch Blevins; 09.12.2009
comment
хех, я думаю, что мы приближаемся к идеальной реализации, поскольку скорость изменений, кажется, замедляется. Вопрос: ваша последняя версия точно работает? Если я правильно понимаю, я думаю, что getMax(Some(2),Some(3)) вернет 2, когда он должен вернуть 3. Возможно, 2-й случай должен быть case ((Some(x)), None)) = › ? - person Alex Black; 09.12.2009
comment
Переписал еще раз, чтобы исключить карту над списком. Обратите внимание, что выбор значений происходит в функции складывания. - person Mitch Blevins; 09.12.2009
comment
+1 для None.asInstanceOf[Option[T]]. Для этой цели я создавал фиктивные вальцы. - person Trenton; 09.12.2009
comment
Просто становится все меньше :) - person Alex Black; 09.12.2009
comment
Могу ли я предложить, чтобы запись None : Option[T] была короче, чем None.asInstanceOf[Option[T]]? Конечно, это может потребовать круглых скобок вокруг него, хотя и не здесь, но все же... - person Daniel C. Sobral; 09.12.2009
comment
Отредактировано, чтобы отразить комментарии Даниэля. - person Mitch Blevins; 09.12.2009

С Numeric[T] в 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>
person Eastsun    schedule 09.12.2009

Как ответил Митч Блевинс, именно для этого были созданы границы просмотра. Теперь, хотя Numeric добавлено только в Scala 2.8, это не значит, что оно не зависит от какой-либо функции Scala 2.8. То же самое можно было бы сделать и на Scala 2.7.

Вас могут заинтересовать некоторые общие фрагменты кода, которые я написал для Rosetta Code:

person Daniel C. Sobral    schedule 09.12.2009