Вернуть универсальный Traversable указанного типа - PullRequest
0 голосов
/ 05 июня 2019

Я бы хотел иметь возможность в общем манипулировать типами, такими как T[_] <: Traversable, чтобы я мог делать такие вещи, как отображение и фильтрация, но я бы хотел отложить решение о том, какой Traversable я выберу, как можно дольше .

Я хотел бы иметь возможность писать функции для обобщенного T[Int], который возвращает T[Int], а не Traversable[Int]. Например, я хотел бы применить функцию к Set[Int] или Vector[Int] или ко всему, что расширяет Traversable, и вернуть этот тип обратно.

Сначала я попытался сделать это простым способом, например:

trait CollectionHolder[T[_] <: Traversable[_]] {

  def easyLessThanTen(xs: T[Int]): T[Int] = {
    xs.filter(_ < 10)
  }
}

но это не скомпилируется: отсутствует тип параметра для расширенной функции. Однако он скомпилируется, если функция принимает Traversable[Int] вместо T[Int], поэтому я подумал, что могу работать с Traversable и преобразовать в T. Это привело меня к CanBuildFrom

object DoingThingsWithTypes {    

  trait CollectionHolder[T[_] <: Traversable[_]] {

    def lessThanTen(xs: T[Int])(implicit cbf: CanBuildFrom[Traversable[Int], Int, T[Int]]): T[Int] = {

      val filteredTraversable = xs.asInstanceOf[Traversable[Int]].filter(_ < 10)

      (cbf() ++= filteredTraversable).result
}

, который компилируется. Но тогда в моих тестах:

val xs = Set(1, 2, 3, 4, 1000)

object withSet extends CollectionHolder[Set]

withSet.lessThanTen(xs) shouldBe Set(1, 2, 3, 4)

Я получаю следующую ошибку компилятора:

Невозможно создать коллекцию типа Set [Int] с элементами типа Int основано на коллекции типа Traversable [Int]. недостаточно аргументы для метода lessThanTen: (неявный cbf: scala.collection.generic.CanBuildFrom [Traversable [Int], Int, Set [Int]]) Установите [Int]. Не указано значение параметра cbf.

Где я могу получить CanBuildFrom для этого преобразования? Или еще лучше, как я могу изменить свой более простой подход для достижения желаемого результата? Или мне нужно использовать класс типов и написать неявную реализацию для каждого Traversable, который я хочу использовать (один для Set, один для Vector и т. Д.)? Я предпочел бы избежать последнего подхода, если это возможно.

Ответы [ 2 ]

2 голосов
/ 05 июня 2019

Использование стандартной библиотеки (Scala 2.12.8) вместо cats / scalaz / etc. вам нужно посмотреть на GenericTraversableTemplate. filter там не определено, но может быть легко:

import scala.collection.GenTraversable
import scala.collection.generic.GenericTraversableTemplate

trait CollectionHolder[T[A] <: GenTraversable[A] with GenericTraversableTemplate[A, T]] {

  def lessThanTen(xs: T[Int]): T[Int] = {
    filter(xs)(_ < 10)
  }

  def filter[A](xs: T[A])(pred: A => Boolean) = {
    val builder = xs.genericBuilder[A]
    xs.foreach(x => if (pred(x)) { builder += x })
    builder.result()
  }
}

В комментарии вы упоминаете nonEmpty и exists; они доступны из-за привязки типа GenTraversable. На самом деле filter тоже, проблема в том, что он возвращает GenTraversable[A] вместо T[A].

Scala 2.13 перерабатывает коллекции, поэтому методы там, вероятно, будут немного отличаться, но я еще недостаточно изучил их.

Также: T[_] <: Traversable[_] скорее всего не то, что вам нужно, в отличие от T[A] <: Traversable[A]; например первое ограничение не нарушается, если у вас есть T[Int] <: Traversable[String].

1 голос
/ 05 июня 2019

Да, я говорю, что вы должны использовать классы типов .
Но вам не нужно реализовывать их или предоставлять их экземпляры для нужных вам типов.Как, они очень распространены и могут быть найдены в библиотеках , таких как cats или scalaz.

Например, используя cats:

import cats.{Traverse, TraverseFilter}
import cats.syntax.all._ // Provides the nonEmpty, filter & map extension methods to C.

import scala.language.higherKinds

def algorithm[C[_]: TraverseFilter: Traverse](col: C[Int]): C[Int] =
  if (col.nonEmpty)
    col.filter(x => x < 10)
  else
    col.map(x => x * 2) // nonsense, but just to show that you can use map too.

Что вы можете использовать следующим образом:

import cats.instances.list._

algorithm(List(1, 200, 3, 100))
// res: List[Int] = List(1, 3)

Возможно, стоит добавить, что существует множество других методов, таких как exists, foldLeft, size и т. Д.
Взятьсм. документацию .И если вы впервые используете cats или scalaz или эти концепции в целом, вы можете найти scala-with-cats очень поучительным.

...