Restituzione del tipo di raccolta originale nel metodo generico

Diciamo che vogliamo creare una funzione come minBy che restituisca tutti gli elementi di un minimalismo uguale in una raccolta:

 def multiMinBy[A, B: Ordering](xs: Traversable[A])(f: A => B) = { val minVal = f(xs minBy f) xs filter (f(_) == minVal) } scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last) res33: Traversable[java.lang.String] = List(zza, zzza) 

Fin qui, tutto bene, tranne che abbiamo un retro Traversable invece della nostra List iniziale.

Così ho provato a cambiare la firma in

 def multiMinBy[A, B: Ordering, C  B) 

nella speranza di ottenere un ritorno in C piuttosto che un Traversable[A] . Tuttavia, non ottengo nulla indietro:

 scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last) :9: error: inferred type arguments [Nothing,Nothing,List[java.lang.String]] do not conform to method multiMinBy's type parameter bounds [A,B,C <: Traversable[A]] 

Penso che questo sia dovuto al fatto che C compare negli argomenti prima che A sia stato dedotto? Così ho invertito l’ordine degli argomenti e aggiunto un cast:

 def multiMinBy[A, B: Ordering, C  B)(xs: C) = { val minVal = f(xs minBy f) (xs filter (f(_) == minVal)).asInstanceOf[C] } 

che funziona, tranne che dobbiamo chiamarlo così:

 multiMinBy((x: String) => x.last)(List("zza","zzza","zzb","zzzb")) 

Esiste un modo per mantenere la syntax originale, ripristinando il giusto tipo di raccolta?

Penso che la soluzione di Miles Sabin sia troppo complessa. La collezione di Scala ha già i macchinari necessari per farlo funzionare, con un piccolo cambiamento:

 import scala.collection.TraversableLike def multiMinBy[A, B: Ordering, C <: Traversable[A]] (xs: C with TraversableLike[A, C]) (f: A => B): C = { val minVal = f(xs minBy f) xs filter (f(_) == minVal) } 

Che ne dici di usare CanBuildFrom ?

 import scala.collection.immutable._ import scala.collection.generic._ def multiMinBy[A, B, From[X] <: Traversable[X], To](xs: From[A])(f: A => B) (implicit ord: Ordering[B], bf: CanBuildFrom[From[_], A, To]) = { val minVal = f(xs minBy f) val b = bf() b ++= (xs filter (f(_) == minVal)) b.result } scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last) res1: List[java.lang.String] = List(zza, zzza) 

Il tuo problema è che quando viene visualizzato come metodo su GenTraversable[A] (che userò al posto di Traversable[A] in questa risposta) il tipo di risultato del metodo di filter non è più preciso di GenTraversable[A] . Sfortunatamente all’interno del corpo del metodo multiMinBy come scritto è tutto quello che sai su xs .

Per ottenere il risultato che stai multiMinBy più precisa la firma di multiMinBy . Un modo per farlo mentre si lascia relativamente aperto il tipo di contenitore è usare un tipo strutturale come segue,

 type HomFilter[CC[X] <: GenTraversable[X], A] = CC[A] { def filter(p : A => Boolean) : CC[A] } def multiMinBy[CC[X] <: GenTraversable[X], A, B: Ordering] (xs: HomFilter[CC, A])(f: A => B) : CC[A] = { val minVal = f(xs minBy f) xs filter (f(_) == minVal) } 

Il tipo strutturale HomFilter ci consente di affermare che l’argomento di multiMinBy deve avere un metodo di filter con il tipo di risultato desiderato.

Esempio di sessione REPL,

 scala> val mmb = multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last) mmb: List[String] = List(zza, zzza) 

Tieni a mente che questo è un requisito più severo di quello che il container sia solo attraversabile: è consentito per i sottotipi di GenTraversable definire i metodi di filter che non sono regolari in questo modo. La firma sopra impedirà staticamente che i valori di tali tipi vengano passati a multiMinBy … presumibilmente questo è il comportamento che stai multiMinBy .