Смешивание общих признаков в параметризованных классах без дублирования параметров типа - PullRequest
4 голосов
/ 17 февраля 2011

Давайте предположим, что я хочу создать черту, которую я могу смешать с любым Traversable [T]. В конце я хочу сказать что-то вроде:

val m = Map("name" -> "foo") with MoreFilterOperations

и есть методы для MoreFilterOperations, которые выражены во всем, что может предложить Traversable, например:

def filterFirstTwo(f: (T) => Boolean) = filter(f) take 2

Однако проблема состоит в том, что T не определен как параметр типа в MoreFilterOperations. Как только я это сделаю, это, конечно, выполнимо, но тогда мой код будет выглядеть так:

val m = Map("name" -> "foo") with MoreFilterOperations[(String,String)]

или если я определю переменную этого типа:

var m2: Map[String,String] with MoreFilterOperations[(String,String)] = ...

что является способом многословия на мой вкус. Я хотел бы, чтобы черта была определена таким образом, чтобы я мог написать последнее как:

var m2: Map[String,String] with MoreFilterOperations

Я пробовал собственные типы, члены абстрактных типов, но ничего полезного не принесло. Есть какие-нибудь подсказки?

Ответы [ 3 ]

11 голосов
/ 17 февраля 2011

Map("name" -> "foo") является вызовом функции, а не конструктором, это означает, что вы не можете написать:

Map("name" -> "foo") with MoreFilterOperations

больше, что вы можете написать

val m = Map("name" -> "foo")
val m2 = m with MoreFilterOperations

Чтобы получитьЧто касается миксина, вы должны использовать конкретный тип, наивная первая попытка будет выглядеть примерно так:

def EnhMap[K,V](entries: (K,V)*) =
  new collection.immutable.HashMap[K,V] with MoreFilterOptions[(K,V)] ++ entries

Использование здесь фабричного метода, чтобы избежать дублирования параметров типа.Тем не менее, это не сработает, потому что метод ++ просто собирается вернуть старый добрый HashMap без mixin!

Решение (как предположил Сэм) состоит в том, чтобы использовать неявное преобразование вдобавить сутенер метод.Это позволит вам трансформировать карту всеми обычными методами, и при этом вы сможете использовать дополнительные методы на получившейся карте.Обычно я делаю это с классом, а не с чертой, так как наличие параметров конструктора приводит к более чистому синтаксису:

class MoreFilterOperations[T](t: Traversable[T]) {
  def filterFirstTwo(f: (T) => Boolean) = t filter f take 2
}

object MoreFilterOperations {
  implicit def traversableToFilterOps[T](t:Traversable[T]) =
    new MoreFilterOperations(t)
}

Это позволяет вам писать

val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3")
val m2 = m filterFirstTwo (_._1.startsWith("n"))

Ноэто все еще не играет хорошо с структурой коллекций.Вы начали с карты и получили Traversable.Это не то, как все должно работать.Хитрость здесь в том, чтобы также абстрагироваться от типа коллекции, используя типы с более высоким родом

import collection.TraversableLike

class MoreFilterOperations[Repr <% TraversableLike[T,Repr], T] (xs: Repr) {
  def filterFirstTwo(f: (T) => Boolean) = xs filter f take 2
}

Достаточно просто.Вы должны указать Repr, тип, представляющий коллекцию, и T, тип элементов.Я использую TraversableLike вместо Traversable, поскольку он встраивает свое представление;без этого filterFirstTwo вернет Traversable независимо от начального типа.

Теперь неявные преобразования.Это где вещи становятся немного хитрее в нотации типов.Во-первых, я использую тип с более высоким родом для захвата представления коллекции: CC[X] <: Traversable[X], этот параметр задает тип CC, который должен быть подклассом Traversable (обратите внимание на использование X в качестве заполнителя здесь, CC[_] <: Traversable[_] не означает то же самое).

Существует также неявный CC[T] <:< TraversableLike[T,CC[T]], который компилятор использует для статической гарантии того, что наша коллекция CC[T] действительно является подклассом TraversableLike, и поэтомудопустимый аргумент для конструктора MoreFilterOperations:

object MoreFilterOperations {
  implicit def traversableToFilterOps[CC[X] <: Traversable[X], T]
  (xs: CC[T])(implicit witness: CC[T] <:< TraversableLike[T,CC[T]]) =
    new MoreFilterOperations[CC[T], T](xs)
}

Пока все хорошо.Но есть еще одна проблема ... Это не будет работать с картами, потому что они принимают два параметра типа.Решение состоит в том, чтобы добавить еще один неявный объект MoreFilterOperations, используя те же принципы, что и раньше:

implicit def mapToFilterOps[CC[KX,VX] <: Map[KX,VX], K, V]
(xs: CC[K,V])(implicit witness: CC[K,V] <:< TraversableLike[(K,V),CC[K,V]]) =
  new MoreFilterOperations[CC[K,V],(K,V)](xs)

Настоящая красота возникает, когда вы также хотите работать с типами, которые на самом деле не являются коллекциями,но можно рассматривать, как если бы они были.Помните Repr <% TraversableLike в конструкторе MoreFilterOperations?Это ограничение вида и позволяет типы, которые могут быть неявно преобразованы в TraversableLike, а также прямые подклассы.Строки являются классическим примером этого:

implicit def stringToFilterOps
(xs: String)(implicit witness: String <%< TraversableLike[Char,String])
: MoreFilterOperations[String, Char] =
  new MoreFilterOperations[String, Char](xs)

Если вы теперь запустите его на REPL:

val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3")
//  m: scala.collection.immutable.Map[java.lang.String,java.lang.String] =
//    Map((name,foo), (name2,foo2), (name3,foo3))

val m2 = m filterFirstTwo (_._1.startsWith("n"))
//  m2: scala.collection.immutable.Map[java.lang.String,java.lang.String] =
//    Map((name,foo), (name2,foo2))

"qaxfwcyebovjnbointofm" filterFirstTwo (_ < 'g')
//res5: String = af

Карта входит, Карта выходит.Строка входит, Строка выходит.и т.д ...

Я еще не пробовал с Stream, или Set, или Vector, но вы можете быть уверены, что если вы это сделаете, он вернет то же самоетип коллекции, с которой вы начали.

2 голосов
/ 17 февраля 2011

Это не совсем то, что вы просили, но вы можете решить эту проблему с последствиями:

trait MoreFilterOperations[T] {
  def filterFirstTwo(f: (T) => Boolean) = traversable.filter(f) take 2
  def traversable:Traversable[T]
}

object FilterImplicits {
  implicit def traversableToFilterOps[T](t:Traversable[T]) = new MoreFilterOperations[T] { val traversable = t }
}

object test {

  import FilterImplicits._

  val m = Map("name" -> "foo", "name2" -> "foo2", "name3" -> "foo3")
  val r = m.filterFirstTwo(_._1.startsWith("n"))
}

scala> test.r
res2: Traversable[(java.lang.String, java.lang.String)] = Map((name,foo), (name2,foo2))
1 голос
/ 17 февраля 2011

Стандартная библиотека Scala использует для этой цели импликации. Например. "123".toInt. Я думаю, что это лучший способ в этом случае.

В противном случае вам придется выполнить полную реализацию вашей «карты с дополнительными операциями», поскольку неизменяемые коллекции требуют создания новых экземпляров вашего нового смешанного класса.

С изменяемыми коллекциями вы можете сделать что-то вроде этого:

object FooBar {
  trait MoreFilterOperations[T] {
    this: Traversable[T] =>
    def filterFirstTwo(f: (T) => Boolean) = filter(f) take 2
  }

  object moreFilterOperations {
    def ~:[K, V](m: Map[K, V]) = new collection.mutable.HashMap[K, V] with MoreFilterOperations[(K, V)] {
      this ++= m
    }
  }

  def main(args: Array[String]) {
    val m = Map("a" -> 1, "b" -> 2, "c" -> 3) ~: moreFilterOperations
    println(m.filterFirstTwo(_ => true))
  }
}

Я бы лучше использовал импликации.

...