2009-12-08 8 views
7

En Scala, j'aimerais pouvoir écrire des classes génériques qui utilisent des opérateurs comme>, /, * etc, mais je ne vois pas comment contraindre T de telle sorte que cela fonctionne. J'ai regardé dans T contraignant avec Ordered [T], mais cela ne semble pas fonctionner puisque seulement RichXXX (par exemple RichInt) l'étendent, pas Int etc. J'ai également vu Numeric [T], est-ce seulement disponible dans Scala 2.8?Scala: Quel est le meilleur moyen d'effectuer des opérations numériques dans des classes génériques?

Voici un exemple précis:

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

Cette classe ne compile pas, car il y a beaucoup Ts qui ne prennent pas en charge> etc.

pensées?

Pour l'instant, j'ai utilisé un trait MixIn pour contourner ceci:

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

qui si nous modifions la classe d'origine peut être mélangé au moment de l'instanciation.

P.S. Mitch, inspiré par votre re-écriture de getMax, voici une autre:

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 
    } 

Répondre

9

Vous pouvez utiliser View Bounds.

En résumé, def foo[T <% U](t: T) est une fonction qui prendra tout T qui est ou peut être implicitement converti en U. Puisqu'un Int peut être converti en un RichInt (qui contient la méthode désirée), ceci est un excellent exemple d'utilisation.

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 - Je réécris votre méthode getMax (...) pour comparer les valeurs au lieu des options elles-mêmes, et le modèle utilisé correspondant au lieu de isDefined (...)

PPS - Scala 2.8 sera avoir un trait numérique qui peut être utile. http://article.gmane.org/gmane.comp.lang.scala/16608


Addendum

Juste pour rire, voici la version super-compact qui élimine la méthode getMax tout à fait:

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

Encore un autre additif

Cette version serait plus efficace pour les grandes listes ... évite la création de certains (x) pour chaque élément:

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

dernier, je te le promets!

À ce stade, vous pouvez juste fossé de la classe et utiliser une fonction:

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

Merci encore Mitch. Je jure que j'ai essayé ça et que je n'ai pas pu le compiler! –

+0

J'ai ajouté une mise à jour à la question originale avec une autre version de getMax inspirée par la vôtre. –

+0

J'ai mis à jour mon getMax avec encore une autre version inspirée par la vôtre. Combien d'autres itérations avant d'atteindre une sorte de singularité? –

2

Avec Numeric[T] dans 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> 
1

Comme répondu par Mitch Blevins, c'est ce que limites de vue ont été faites pour. Maintenant, alors que Numeric est quelque chose ajouté uniquement sur Scala 2.8, pas que cela ne dépend pas de toute fonctionnalité Scala 2.8. On pourrait faire la même chose sur Scala 2.7.

Vous pouvez être intéressé par quelques extraits de code générique j'ai écrit pour Rosetta Code:

Questions connexes