Mixing common characteristics in parameterized classes without duplicating type parameters

Suppose I want to create a trait with which I can move in any Traversable [T]. In the end, I want to say things like:

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

and have methods in MoreFilterOperations that express themselves in everything Traversable has to offer, for example:

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

However, the problem is that T is not defined as a type parameter in MoreFilterOperations. As soon as I do this, of course, of course, but then my code will read:

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

or if I define a variable of this type:

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

which is a detailed way for my taste. I would like this trait to be defined in such a way that I could write it as:

 var m2: Map[String,String] with MoreFilterOperations 

I tried self types, abstract type members, but that didn't bring anything useful. Any clues?

+4
source share
3 answers

Map("name" -> "foo") is a function call, not a constructor, this means you cannot write:

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

what can you write

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

To get mixin, you must use a specific type, the naive first attempt would be something like this:

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

Using the factory method here to avoid duplication of type parameters. However, this will not work, because the ++ method will simply return a plain old HashMap without mixin!

The solution (as Sam suggested) involves using an implicit conversion to add the pimped method. This will allow you to convert the map with all the usual methods and still use your additional methods on the resulting map. I usually do this with the class instead of the attribute, since having the available constructor parameters leads to a stronger syntax:

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

It allows you to write

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

But he still doesn't work very well with the collection map. You started with a map and ended with Traversable . It's not about how things should work. The trick here is also to abstract by type of collection using higher types

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

Simple enough. You must specify Repr , the type representing the collection, and T , the type of elements. I use TraversableLike instead of Traversable as it implements its view; without it, filterFirstTwo will return Traversable regardless of the type of start.

Now implicit conversions. This is where things get a little more complicated in type notation. First, I use a higher type to capture the collection view: CC[X] <: Traversable[X] , this parameter indicates the type CC , which should be a subclass of Traversable (note the use of X as a placeholder here, CC[_] <: Traversable[_] does not mean the same thing).

There is also an implicit CC[T] <:< TraversableLike[T,CC[T]] , which the compiler uses to statically ensure that our CC[T] collection is indeed a subclass of TraversableLike and therefore a valid argument for the MoreFilterOperations constructor:

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

So far so good. But there is one more problem ... It will not work with cards, because they take two types of parameters. The solution is to add another implicit MoreFilterOperations object using the same principles as before:

 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) 

Real beauty comes when you also want to work with types that are not really collections, but that can be displayed as if they were. Remember the Repr <% TraversableLike in the MoreFilterOperations constructor? This view restriction also allows types that can be implicitly converted to TraversableLike , as well as direct subclasses. Lines are a classic example of this:

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

If you run it now in 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 

The card is coming, the card is coming out. The line goes, the line goes. etc...

I have not tried it with Stream or Set or Vector , but you can be sure that if you did, it would return the same type of collection that you started working with.

+11
source

This is not quite what you requested, but you can solve this problem with implicits:

 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)) 
+2
source

The Scala standard library uses implicits for this purpose. For instance. "123".toInt . I think this is the best way in this case.

Otherwise, you will have to go through the full implementation of your β€œcard with additional operations,” since immutable collections require the creation of new instances of your new mixed class.

With volatile collections, you can do something like this:

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

I would rather use implicits.

+1
source

Source: https://habr.com/ru/post/1340132/


All Articles