Возвращение исходного типа коллекции в универсальном методе - PullRequest
17 голосов
/ 23 ноября 2011

Скажем, мы хотим создать функцию, подобную minBy, которая возвращает все элементы одинакового минимализма в коллекции:

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)

Пока все хорошо, за исключением того, что вместо этого у нас есть Traversableнашего начального List.

Поэтому я попытался изменить подпись на

def multiMinBy[A, B: Ordering, C <: Traversable[A]](xs: C)(f: A => B)

в надежде получить обратно C вместо Traversable[A].Однако я ничего не получаю обратно:

scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last)

<console>: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]]

Я думаю, это потому, что у нас C появляется в аргументах до того, как A будет выведено?Поэтому я переключил порядок аргументов и добавил приведение:

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

, которое работает, за исключением того, что мы должны назвать его так:

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

Есть ли способ сохранитьисходный синтаксис, возвращая правильный тип коллекции?

Ответы [ 3 ]

22 голосов
/ 05 декабря 2011

Я думаю, что решение Miles Sabin слишком сложное. В коллекции Scala уже есть необходимый механизм, чтобы заставить ее работать, с небольшим изменением:

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)
}
13 голосов
/ 23 ноября 2011

Как насчет использования 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)
10 голосов
/ 23 ноября 2011

Ваша проблема в том, что при просмотре в качестве метода на GenTraversable[A] (который я буду использовать вместо Traversable[A] в этом ответе) тип результата метода filter не более точен, чем GenTraversable[A].К сожалению, как написано в теле метода multiMinBy, это все, что вы знаете о xs.

Чтобы получить желаемый результат, вам нужно будет сделать подпись multiMinBy более точной.Один из способов сделать это, оставляя тип контейнера относительно открытым, - это использовать структурный тип следующим образом:

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

Структурный тип HomFilter позволяет нам утверждать, что аргумент multiMinBy должен иметьfilter метод с желаемым типом результата.

Пример сеанса REPL,

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

Имейте в виду, что это более строгое требование, чем просто контейнер Traversable: этодопустимо для подтипов GenTraversable определять filter методы, которые не являются регулярными в этом смысле.Сигнатура выше будет статически предотвращать передачу значений таких типов в multiMinBy ... предположительно, это то поведение, которое вам нужно.

...