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
.