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
, но вы можете быть уверены, что если вы это сделаете, он вернет то же самоетип коллекции, с которой вы начали.